From 7366e094a1135c2f8f4cd241d4f7addea13baefc Mon Sep 17 00:00:00 2001 From: klein panic Date: Thu, 8 Aug 2024 15:30:46 -0400 Subject: [PATCH] Initial Commit --- KleinDwm/LICENSE | 38 + KleinDwm/README | 48 + KleinDwm/README.md | 44 + KleinDwm/misc/.bashrc | 117 + KleinDwm/misc/.profile | 32 + KleinDwm/misc/.xinitrc | 22 + KleinDwm/source/IPCClient.c | 66 + KleinDwm/source/IPCClient.h | 61 + KleinDwm/source/Makefile | 55 + KleinDwm/source/colors/default.h | 34 + KleinDwm/source/colors/space.h | 34 + KleinDwm/source/config.def.h | 62 + KleinDwm/source/config.def.h.orig | 44 + KleinDwm/source/config.def.h.rej | 11 + KleinDwm/source/config.h | 64 + KleinDwm/source/config.mk | 42 + KleinDwm/source/config.mk.orig | 39 + KleinDwm/source/config.mk.rej | 10 + KleinDwm/source/drw.c | 525 +++ KleinDwm/source/drw.c.orig | 450 +++ KleinDwm/source/drw.h | 62 + KleinDwm/source/drw.h.orig | 58 + KleinDwm/source/drw.o | Bin 0 -> 13728 bytes KleinDwm/source/dwm | Bin 0 -> 114296 bytes KleinDwm/source/dwm-msg | Bin 0 -> 17896 bytes KleinDwm/source/dwm-msg.c | 548 +++ KleinDwm/source/dwm-msg.o | Bin 0 -> 14040 bytes KleinDwm/source/dwm.1 | 219 ++ KleinDwm/source/dwm.1.orig | 209 ++ KleinDwm/source/dwm.c | 3098 ++++++++++++++++ KleinDwm/source/dwm.c.orig | 2965 +++++++++++++++ KleinDwm/source/dwm.c.rej | 53 + KleinDwm/source/dwm.o | Bin 0 -> 128152 bytes KleinDwm/source/fibonacci.c | 66 + KleinDwm/source/ipc.c | 1202 ++++++ KleinDwm/source/ipc.h | 320 ++ KleinDwm/source/keys.h | 121 + KleinDwm/source/layouts.h | 20 + KleinDwm/source/movestack.c | 48 + KleinDwm/source/patches/README.md | 103 + KleinDwm/source/patches/accessnthmon.diff | 100 + .../source/patches/dwm-6.2-urg-border.diff | 56 + .../dwm-alwayscenter-20200625-f04cac6.diff | 12 + .../dwm-autostart-20210120-cb3f58a.diff | 179 + .../patches/dwm-bar-height-spacing-6.3.diff | 25 + .../patches/dwm-centerfirstwindow-6.2.diff | 67 + ...-clientmonoclesymbol-20220417-d93ff48.diff | 41 + .../dwm-fakefullscreen-20210714-138b405.diff | 120 + .../source/patches/dwm-fibonacci-6.2.diff | 114 + .../dwm-fullgaps-20200508-7b77734.diff | 138 + .../patches/dwm-ipc-20201106-f04cac6.diff | 3246 +++++++++++++++++ .../dwm-keychain-20200729-053e3a2.diff | 266 ++ .../dwm-movestack-20211115-a786211.diff | 95 + .../patches/dwm-pertag-20200914-61bb8b2.diff | 177 + .../source/patches/dwm-rainbowtags-6.2.diff | 59 + .../patches/dwm-restartsig-20180523-6.2.diff | 139 + .../patches/dwm-status2d-systray-6.3.diff | 888 +++++ .../dwm-titlecolor-20210815-ed3ab6b4.diff | 47 + .../source/patches/dwm-winicon-6.3-v2.1.diff | 371 ++ KleinDwm/source/tags | 496 +++ KleinDwm/source/transient.c | 42 + KleinDwm/source/util.c | 171 + KleinDwm/source/util.c.orig | 36 + KleinDwm/source/util.h | 18 + KleinDwm/source/util.o | Bin 0 -> 4488 bytes KleinDwm/source/yajl_dumps.c | 351 ++ KleinDwm/source/yajl_dumps.h | 65 + customstatusbar/statusbars/colorvars.sh | 11 + customstatusbar/statusbars/install.sh | 38 + customstatusbar/statusbars/oldstatus2dbar.sh | 328 ++ customstatusbar/statusbars/statusbar.sh | 220 ++ .../systrays/brightnesssystray2.sh | 51 + .../systrays/brightnesssystray2.sh.bak | 37 + customstatusbar/systrays/expansivesystray.sh | 17 + customstatusbar/systrays/screenshotsystray.sh | 43 + customstatusbar/systrays/startupscript.sh | 4 + 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 -> 43584 bytes dmenu/dmenu.1 | 199 + dmenu/dmenu.c | 838 +++++ dmenu/dmenu.c.orig | 820 +++++ dmenu/dmenu.c.rej | 20 + dmenu/dmenu.o | Bin 0 -> 34072 bytes dmenu/dmenu_path | 13 + dmenu/dmenu_run | 2 + dmenu/drw.c | 451 +++ dmenu/drw.h | 58 + dmenu/drw.o | Bin 0 -> 10872 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 -> 16888 bytes dmenu/stest.1 | 90 + dmenu/stest.c | 109 + dmenu/stest.o | Bin 0 -> 4952 bytes dmenu/util.c | 36 + dmenu/util.h | 9 + dmenu/util.o | Bin 0 -> 2080 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 -> 57104 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 -> 110696 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 -> 87168 bytes st/win.h | 41 + st/x.c | 2107 +++++++++++ st/x.o | Bin 0 -> 73896 bytes 197 files changed, 44569 insertions(+) create mode 100644 KleinDwm/LICENSE create mode 100644 KleinDwm/README create mode 100644 KleinDwm/README.md create mode 100644 KleinDwm/misc/.bashrc create mode 100644 KleinDwm/misc/.profile create mode 100644 KleinDwm/misc/.xinitrc create mode 100644 KleinDwm/source/IPCClient.c create mode 100644 KleinDwm/source/IPCClient.h create mode 100644 KleinDwm/source/Makefile create mode 100644 KleinDwm/source/colors/default.h create mode 100644 KleinDwm/source/colors/space.h create mode 100644 KleinDwm/source/config.def.h create mode 100644 KleinDwm/source/config.def.h.orig create mode 100644 KleinDwm/source/config.def.h.rej create mode 100644 KleinDwm/source/config.h create mode 100644 KleinDwm/source/config.mk create mode 100644 KleinDwm/source/config.mk.orig create mode 100644 KleinDwm/source/config.mk.rej create mode 100644 KleinDwm/source/drw.c create mode 100644 KleinDwm/source/drw.c.orig create mode 100644 KleinDwm/source/drw.h create mode 100644 KleinDwm/source/drw.h.orig create mode 100644 KleinDwm/source/drw.o create mode 100755 KleinDwm/source/dwm create mode 100755 KleinDwm/source/dwm-msg create mode 100644 KleinDwm/source/dwm-msg.c create mode 100644 KleinDwm/source/dwm-msg.o create mode 100644 KleinDwm/source/dwm.1 create mode 100644 KleinDwm/source/dwm.1.orig create mode 100644 KleinDwm/source/dwm.c create mode 100644 KleinDwm/source/dwm.c.orig create mode 100644 KleinDwm/source/dwm.c.rej create mode 100644 KleinDwm/source/dwm.o create mode 100644 KleinDwm/source/fibonacci.c create mode 100644 KleinDwm/source/ipc.c create mode 100644 KleinDwm/source/ipc.h create mode 100644 KleinDwm/source/keys.h create mode 100644 KleinDwm/source/layouts.h create mode 100644 KleinDwm/source/movestack.c create mode 100644 KleinDwm/source/patches/README.md create mode 100644 KleinDwm/source/patches/accessnthmon.diff create mode 100644 KleinDwm/source/patches/dwm-6.2-urg-border.diff create mode 100644 KleinDwm/source/patches/dwm-alwayscenter-20200625-f04cac6.diff create mode 100644 KleinDwm/source/patches/dwm-autostart-20210120-cb3f58a.diff create mode 100644 KleinDwm/source/patches/dwm-bar-height-spacing-6.3.diff create mode 100644 KleinDwm/source/patches/dwm-centerfirstwindow-6.2.diff create mode 100644 KleinDwm/source/patches/dwm-clientmonoclesymbol-20220417-d93ff48.diff create mode 100644 KleinDwm/source/patches/dwm-fakefullscreen-20210714-138b405.diff create mode 100644 KleinDwm/source/patches/dwm-fibonacci-6.2.diff create mode 100644 KleinDwm/source/patches/dwm-fullgaps-20200508-7b77734.diff create mode 100644 KleinDwm/source/patches/dwm-ipc-20201106-f04cac6.diff create mode 100644 KleinDwm/source/patches/dwm-keychain-20200729-053e3a2.diff create mode 100644 KleinDwm/source/patches/dwm-movestack-20211115-a786211.diff create mode 100644 KleinDwm/source/patches/dwm-pertag-20200914-61bb8b2.diff create mode 100644 KleinDwm/source/patches/dwm-rainbowtags-6.2.diff create mode 100644 KleinDwm/source/patches/dwm-restartsig-20180523-6.2.diff create mode 100644 KleinDwm/source/patches/dwm-status2d-systray-6.3.diff create mode 100644 KleinDwm/source/patches/dwm-titlecolor-20210815-ed3ab6b4.diff create mode 100644 KleinDwm/source/patches/dwm-winicon-6.3-v2.1.diff create mode 100644 KleinDwm/source/tags create mode 100644 KleinDwm/source/transient.c create mode 100644 KleinDwm/source/util.c create mode 100644 KleinDwm/source/util.c.orig create mode 100644 KleinDwm/source/util.h create mode 100644 KleinDwm/source/util.o create mode 100644 KleinDwm/source/yajl_dumps.c create mode 100644 KleinDwm/source/yajl_dumps.h create mode 100644 customstatusbar/statusbars/colorvars.sh create mode 100755 customstatusbar/statusbars/install.sh create mode 100755 customstatusbar/statusbars/oldstatus2dbar.sh create mode 100755 customstatusbar/statusbars/statusbar.sh create mode 100755 customstatusbar/systrays/brightnesssystray2.sh create mode 100755 customstatusbar/systrays/brightnesssystray2.sh.bak create mode 100755 customstatusbar/systrays/expansivesystray.sh create mode 100755 customstatusbar/systrays/screenshotsystray.sh create mode 100755 customstatusbar/systrays/startupscript.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/KleinDwm/LICENSE b/KleinDwm/LICENSE new file mode 100644 index 0000000..995172f --- /dev/null +++ b/KleinDwm/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/KleinDwm/README b/KleinDwm/README new file mode 100644 index 0000000..95d4fd0 --- /dev/null +++ b/KleinDwm/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/KleinDwm/README.md b/KleinDwm/README.md new file mode 100644 index 0000000..dfb98ee --- /dev/null +++ b/KleinDwm/README.md @@ -0,0 +1,44 @@ +# ⁩ 0x73hahd's DWM + +This repo is for DWM configuration and contains different themes to switch between them. + +## Calm Room DWM Theme +A DWM theme gives a feeling of calm ♡⁩ + +![](screenshots/dwm-calm-room-theme.png) + +## Color Space + +![](screenshots/alacritty-terminals.png) + +![](screenshots/alacritty-terminal.png) + +## Tokyo DWM Theme +A dark theme for DWM 🌃🌆 + +![](screenshots/dwm.png) + +## Toy Story DWM Theme +A dwm theme that I create reminds me of my childhood days 🙃❤ + +![](screenshots/dwm-toy-story-theme.png) + +--- + +#### Themes Switching: + +1. Change the current `color.h` file in `config.h` +```c + #include "colors/" +``` + +2. Replace `alacritty.yml` and `picom.conf` theme file with previous files + +``` + $ mv /alacritty.yml ~/.config/alacritty/alacritty.yml +``` + +``` + $ mv /picom.conf ~/.config/picom/picom.conf +``` + diff --git a/KleinDwm/misc/.bashrc b/KleinDwm/misc/.bashrc new file mode 100644 index 0000000..75c62d0 --- /dev/null +++ b/KleinDwm/misc/.bashrc @@ -0,0 +1,117 @@ +# ~/.bashrc: executed by bash(1) for non-login shells. +# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) +# for examples + +# If not running interactively, don't do anything +case $- in + *i*) ;; + *) return;; +esac + +# don't put duplicate lines or lines starting with space in the history. +# See bash(1) for more options +HISTCONTROL=ignoreboth + +# append to the history file, don't overwrite it +shopt -s histappend + +# for setting history length see HISTSIZE and HISTFILESIZE in bash(1) +HISTSIZE=1000 +HISTFILESIZE=2000 + +# check the window size after each command and, if necessary, +# update the values of LINES and COLUMNS. +shopt -s checkwinsize + +# If set, the pattern "**" used in a pathname expansion context will +# match all files and zero or more directories and subdirectories. +#shopt -s globstar + +# make less more friendly for non-text input files, see lesspipe(1) +#[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" + +# set variable identifying the chroot you work in (used in the prompt below) +if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then + debian_chroot=$(cat /etc/debian_chroot) +fi + +# set a fancy prompt (non-color, unless we know we "want" color) +case "$TERM" in + xterm-color|*-256color) color_prompt=yes;; +esac + +# uncomment for a colored prompt, if the terminal has the capability; turned +# off by default to not distract the user: the focus in a terminal window +# should be on the output of commands, not on the prompt +force_color_prompt=yes + +if [ -n "$force_color_prompt" ]; then + if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then + # We have color support; assume it's compliant with Ecma-48 + # (ISO/IEC-6429). (Lack of such support is extremely rare, and such + # a case would tend to support setf rather than setaf.) + color_prompt=yes + else + color_prompt= + fi +fi + +if [ "$color_prompt" = yes ]; then + PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' +else + PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' +fi +unset color_prompt force_color_prompt + +# If this is an xterm set the title to user@host:dir +case "$TERM" in +xterm*|rxvt*) + PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" + ;; +*) + ;; +esac + +# enable color support of ls and also add handy aliases +if [ -x /usr/bin/dircolors ]; then + test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" + alias ls='ls --color=auto' + #alias dir='dir --color=auto' + #alias vdir='vdir --color=auto' + + #alias grep='grep --color=auto' + #alias fgrep='fgrep --color=auto' + #alias egrep='egrep --color=auto' +fi + +# colored GCC warnings and errors +#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' + +# some more ls aliases +#alias ll='ls -l' +#alias la='ls -A' +#alias l='ls -CF' + +# Alias definitions. +# You may want to put all your additions into a separate file like +# ~/.bash_aliases, instead of adding them here directly. +# See /usr/share/doc/bash-doc/examples in the bash-doc package. + +if [ -f ~/.bash_aliases ]; then + . ~/.bash_aliases +fi + +# enable programmable completion features (you don't need to enable +# this, if it's already enabled in /etc/bash.bashrc and /etc/profile +# sources /etc/bash.bashrc). +if ! shopt -oq posix; then + if [ -f /usr/share/bash-completion/bash_completion ]; then + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi +fi + +PATH=$PATH:$HOME/.local/bin/ + +export EDITOR=nvim diff --git a/KleinDwm/misc/.profile b/KleinDwm/misc/.profile new file mode 100644 index 0000000..ef3ea0c --- /dev/null +++ b/KleinDwm/misc/.profile @@ -0,0 +1,32 @@ +# ~/.profile: executed by the command interpreter for login shells. +# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login +# exists. +# see /usr/share/doc/bash/examples/startup-files for examples. +# the files are located in the bash-doc package. + +# the default umask is set in /etc/profile; for setting the umask +# for ssh logins, install and configure the libpam-umask package. +#umask 022 + +# if running bash +if [ -n "$BASH_VERSION" ]; then + # include .bashrc if it exists + if [ -f "$HOME/.bashrc" ]; then + . "$HOME/.bashrc" + fi +fi + +# set PATH so it includes user's private bin if it exists +if [ -d "$HOME/bin" ] ; then + PATH="$HOME/bin:$PATH" +fi + +# set PATH so it includes user's private bin if it exists +if [ -d "$HOME/.local/bin" ] ; then + PATH="$HOME/.local/bin:$PATH" +fi + +if [[ -z $DISPLAY && $(tty) == /dev/tty1 ]] +then + exec startx +fi diff --git a/KleinDwm/misc/.xinitrc b/KleinDwm/misc/.xinitrc new file mode 100644 index 0000000..0fa6cab --- /dev/null +++ b/KleinDwm/misc/.xinitrc @@ -0,0 +1,22 @@ +#!/bin/sh +# This is a shell script, read by xinit or startx during the start of the X windows system. File configures the behavior and setup of the X session. + +# Start Picom +picom --config ~/.config/picom/picom.conf & + +# Start feh for bg +feh --bg-scale ~/Pictures/backgrounds/jellyfishbg.jpg + +# Start xrandr + +# Start Statusbar + +# Start First Terminal +(sleep 1 && alacritty -e bash -c "neofetch; lsd; exec bash") & + +# Start Custom SystemTrays +# Start SystemTrays (Not-mine) +pkill volumeicon; sleep 1; volumeicon & +pkill copyq; sleep 2; copyq & +# Start monitoring Scripts +exec dwm diff --git a/KleinDwm/source/IPCClient.c b/KleinDwm/source/IPCClient.c new file mode 100644 index 0000000..0d3eefb --- /dev/null +++ b/KleinDwm/source/IPCClient.c @@ -0,0 +1,66 @@ +#include "IPCClient.h" + +#include +#include + +#include "util.h" + +IPCClient * +ipc_client_new(int fd) +{ + IPCClient *c = (IPCClient *)malloc(sizeof(IPCClient)); + + if (c == NULL) return NULL; + + // Initialize struct + memset(&c->event, 0, sizeof(struct epoll_event)); + + c->buffer_size = 0; + c->buffer = NULL; + c->fd = fd; + c->event.data.fd = fd; + c->next = NULL; + c->prev = NULL; + c->subscriptions = 0; + + return c; +} + +void +ipc_list_add_client(IPCClientList *list, IPCClient *nc) +{ + DEBUG("Adding client with fd %d to list\n", nc->fd); + + if (*list == NULL) { + // List is empty, point list at first client + *list = nc; + } else { + IPCClient *c; + // Go to last client in list + for (c = *list; c && c->next; c = c->next) + ; + c->next = nc; + nc->prev = c; + } +} + +void +ipc_list_remove_client(IPCClientList *list, IPCClient *c) +{ + IPCClient *cprev = c->prev; + IPCClient *cnext = c->next; + + if (cprev != NULL) cprev->next = c->next; + if (cnext != NULL) cnext->prev = c->prev; + if (c == *list) *list = c->next; +} + +IPCClient * +ipc_list_get_client(IPCClientList list, int fd) +{ + for (IPCClient *c = list; c; c = c->next) { + if (c->fd == fd) return c; + } + + return NULL; +} diff --git a/KleinDwm/source/IPCClient.h b/KleinDwm/source/IPCClient.h new file mode 100644 index 0000000..307dfba --- /dev/null +++ b/KleinDwm/source/IPCClient.h @@ -0,0 +1,61 @@ +#ifndef IPC_CLIENT_H_ +#define IPC_CLIENT_H_ + +#include +#include +#include + +typedef struct IPCClient IPCClient; +/** + * This structure contains the details of an IPC Client and pointers for a + * linked list + */ +struct IPCClient { + int fd; + int subscriptions; + + char *buffer; + uint32_t buffer_size; + + struct epoll_event event; + IPCClient *next; + IPCClient *prev; +}; + +typedef IPCClient *IPCClientList; + +/** + * Allocate memory for new IPCClient with the specified file descriptor and + * initialize struct. + * + * @param fd File descriptor of IPC client + * + * @return Address to allocated IPCClient struct + */ +IPCClient *ipc_client_new(int fd); + +/** + * Add an IPC Client to the specified list + * + * @param list Address of the list to add the client to + * @param nc Address of the IPCClient + */ +void ipc_list_add_client(IPCClientList *list, IPCClient *nc); + +/** + * Remove an IPCClient from the specified list + * + * @param list Address of the list to remove the client from + * @param c Address of the IPCClient + */ +void ipc_list_remove_client(IPCClientList *list, IPCClient *c); + +/** + * Get an IPCClient from the specified IPCClient list + * + * @param list List to remove the client from + * @param fd File descriptor of the IPCClient + */ +IPCClient *ipc_list_get_client(IPCClientList list, int fd); + +#endif // IPC_CLIENT_H_ diff --git a/KleinDwm/source/Makefile b/KleinDwm/source/Makefile new file mode 100644 index 0000000..0456754 --- /dev/null +++ b/KleinDwm/source/Makefile @@ -0,0 +1,55 @@ +# dwm - dynamic window manager +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dwm.c util.c +OBJ = ${SRC:.c=.o} + +all: options dwm dwm-msg + +options: + @echo dwm build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + ${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + cp config.def.h $@ + +dwm: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + +dwm-msg: dwm-msg.o + ${CC} -o $@ $< ${LDFLAGS} + +clean: + rm -f dwm dwm-msg ${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 dwm-msg ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/dwm + chmod 755 ${DESTDIR}${PREFIX}/bin/dwm-msg + mkdir -p ${DESTDIR}${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1 + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/dwm\ + ${DESTDIR}${MANPREFIX}/man1/dwm.1 + +.PHONY: all options clean dist install uninstall diff --git a/KleinDwm/source/colors/default.h b/KleinDwm/source/colors/default.h new file mode 100644 index 0000000..009acc3 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/colors/space.h b/KleinDwm/source/colors/space.h new file mode 100644 index 0000000..7972efe --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/config.def.h b/KleinDwm/source/config.def.h new file mode 100644 index 0000000..1f17d59 --- /dev/null +++ b/KleinDwm/source/config.def.h @@ -0,0 +1,62 @@ +/* See LICENSE file for copyright and license details. */ + +#include "colors/space.h" +#include "keys.h" + +#define TERMINAL "alacritty" // default terminal appearance */ +#define ICONSIZE 20 /* icon size */ +#define ICONSPACING 5 /* space between icon and title */ +static const unsigned int borderpx = 2; /* border pixel of windows */ +static const unsigned int gappx = 10; /* gaps between windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */ +static const unsigned int systrayonleft = 0; /* 0: systray in the right corner, >0: 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=11", "DroidSansMono Nerd Font:size=11", "Noto Color Emoji:size=11", "Cairo:size=12" }; +static const int user_bh = 8; /* 2 is the default spacing around the bar's font */ + +/* tagging */ +//static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +//static const char *tags[] = { " \u2680 ", " \u2681 ", " \u2682 ", " \u2683 ", " \u2684 ", " \u2685 ", " \u2661 ", " \u2665 ", "\u266c" }; // Dice Faces +// tag1 tag2 tag3 tag4 tag5 tag6 tag7 tag8 tag9 +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 */ + { "Gimp", NULL, NULL, 0, 1, 0, -1 }, + { "Tk", NULL, NULL, 0, 1, 0, -1 }, + { NULL, NULL, "Brightness Control", 0, 1, 0, -1 }, + { NULL, NULL, "Screenshot Tool", 0, 1, 0, -1 }, + { "Conky", NULL, NULL, 0, 1, 0, -1 }, + { NULL, NULL, "Task Tray", 0, 1, 0, -1 }, + { NULL, NULL, "YAD", 0, 1, 0, -1 }, + { "Ssh_popup", NULL, NULL, 0, 1, 0, -1 }, + { "Conky-Sysinfo", NULL, "Conky", 0, 1, 1, -1 }, +}; + +static const char *ipcsockpath = "/tmp/dwm.sock"; +static IPCCommand ipccommands[] = { + IPCCOMMAND( view, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( toggleview, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( tag, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( toggletag, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( tagmon, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( focusmon, 1, {ARG_TYPE_SINT} ), + IPCCOMMAND( focusstack, 1, {ARG_TYPE_SINT} ), + IPCCOMMAND( zoom, 1, {ARG_TYPE_NONE} ), + IPCCOMMAND( incnmaster, 1, {ARG_TYPE_SINT} ), + IPCCOMMAND( killclient, 1, {ARG_TYPE_SINT} ), + IPCCOMMAND( togglefloating, 1, {ARG_TYPE_NONE} ), + IPCCOMMAND( setmfact, 1, {ARG_TYPE_FLOAT} ), + IPCCOMMAND( setlayoutsafe, 1, {ARG_TYPE_PTR} ), + IPCCOMMAND( quit, 1, {ARG_TYPE_NONE} ) +}; + diff --git a/KleinDwm/source/config.def.h.orig b/KleinDwm/source/config.def.h.orig new file mode 100644 index 0000000..4d31c9f --- /dev/null +++ b/KleinDwm/source/config.def.h.orig @@ -0,0 +1,44 @@ +/* See LICENSE file for copyright and license details. */ + +#include "colors/space.h" +#include "keys.h" + +#define TERMINAL "alacritty" // default terminal appearance */ +#define ICONSIZE 20 /* icon size */ +#define ICONSPACING 5 /* space between icon and title */ +static const unsigned int borderpx = 2; /* border pixel of windows */ +static const unsigned int gappx = 10; /* gaps between windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */ +static const unsigned int systrayonleft = 0; /* 0: systray in the right corner, >0: 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=11", "DroidSansMono Nerd Font:size=11", "Noto Color Emoji:size=11", "Cairo:size=12" }; +static const int user_bh = 8; /* 2 is the default spacing around the bar's font */ + +/* tagging */ +//static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +//static const char *tags[] = { " \u2680 ", " \u2681 ", " \u2682 ", " \u2683 ", " \u2684 ", " \u2685 ", " \u2661 ", " \u2665 ", "\u266c" }; // Dice Faces +// tag1 tag2 tag3 tag4 tag5 tag6 tag7 tag8 tag9 +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 */ + { "Gimp", NULL, NULL, 0, 1, 0, -1 }, + { "Tk", NULL, NULL, 0, 1, 0, -1 }, + { NULL, NULL, "Brightness Control", 0, 1, 0, -1 }, + { NULL, NULL, "Screenshot Tool", 0, 1, 0, -1 }, + { "Conky", NULL, NULL, 0, 1, 0, -1 }, + { NULL, NULL, "Task Tray", 0, 1, 0, -1 }, + { NULL, NULL, "YAD", 0, 1, 0, -1 }, + { "Ssh_popup", NULL, NULL, 0, 1, 0, -1 }, + { "Conky-Sysinfo", NULL, "Conky", 0, 1, 1, -1 }, +}; + diff --git a/KleinDwm/source/config.def.h.rej b/KleinDwm/source/config.def.h.rej new file mode 100644 index 0000000..e10855e --- /dev/null +++ b/KleinDwm/source/config.def.h.rej @@ -0,0 +1,11 @@ +--- config.def.h ++++ 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 */ ++static const int vertpad = 10; /* vertical padding of bar */ ++static const int sidepad = 10; /* horizontal padding of bar */ + static const char *fonts[] = { "monospace:size=10" }; + static const char dmenufont[] = "monospace:size=10"; + static const char col_gray1[] = "#222222"; diff --git a/KleinDwm/source/config.h b/KleinDwm/source/config.h new file mode 100644 index 0000000..52d869a --- /dev/null +++ b/KleinDwm/source/config.h @@ -0,0 +1,64 @@ +/* See LICENSE file for copyright and license details. */ + +#include "colors/space.h" +#include "keys.h" + +#define TERMINAL "alacritty" // default terminal appearance */ +#define ICONSIZE 20 /* icon size */ +#define ICONSPACING 5 /* space between icon and title */ +static const unsigned int borderpx = 2; /* border pixel of windows */ +static const unsigned int gappx = 10; /* gaps between windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */ +static const unsigned int systrayonleft = 0; /* 0: systray in the right corner, >0: 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=11", "DroidSansMono Nerd Font:size=11", "Noto Color Emoji:size=11", "Cairo:size=12" }; +static const int user_bh = 8; /* 2 is the default spacing around the bar's font */ + +/* tagging */ +//static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +//static const char *tags[] = { " \u2680 ", " \u2681 ", " \u2682 ", " \u2683 ", " \u2684 ", " \u2685 ", " \u2661 ", " \u2665 ", "\u266c" }; // Dice Faces +// tag1 tag2 tag3 tag4 tag5 tag6 tag7 tag8 tag9 +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 */ + { "Gimp", NULL, NULL, 0, 1, 0, -1 }, + { "Tk", NULL, NULL, 0, 1, 0, -1 }, + { NULL, NULL, "Brightness Control", 0, 1, 0, -1 }, + { NULL, NULL, "Screenshot Tool", 0, 1, 0, -1 }, + { "Conky", NULL, NULL, 0, 1, 0, -1 }, + { NULL, NULL, "Task Tray", 0, 1, 0, -1 }, + { NULL, NULL, "YAD", 0, 1, 0, -1 }, + { "Ssh_popup", NULL, NULL, 0, 1, 0, -1 }, + { "Conky-Sysinfo", NULL, "Conky", 0, 1, 1, -1 }, + { "BrightnessControl", NULL, NULL, 0, 1, 1 -1 }, + { "Arduino", NULL, NULL, 0, 1, 1, -1 }, +}; + +static const char *ipcsockpath = "/tmp/dwm.sock"; +static IPCCommand ipccommands[] = { + IPCCOMMAND( view, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( toggleview, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( tag, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( toggletag, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( tagmon, 1, {ARG_TYPE_UINT} ), + IPCCOMMAND( focusmon, 1, {ARG_TYPE_SINT} ), + IPCCOMMAND( focusstack, 1, {ARG_TYPE_SINT} ), + IPCCOMMAND( zoom, 1, {ARG_TYPE_NONE} ), + IPCCOMMAND( incnmaster, 1, {ARG_TYPE_SINT} ), + IPCCOMMAND( killclient, 1, {ARG_TYPE_SINT} ), + IPCCOMMAND( togglefloating, 1, {ARG_TYPE_NONE} ), + IPCCOMMAND( setmfact, 1, {ARG_TYPE_FLOAT} ), + IPCCOMMAND( setlayoutsafe, 1, {ARG_TYPE_PTR} ), + IPCCOMMAND( quit, 1, {ARG_TYPE_NONE} ) +}; + diff --git a/KleinDwm/source/config.mk b/KleinDwm/source/config.mk new file mode 100644 index 0000000..7d5ec8b --- /dev/null +++ b/KleinDwm/source/config.mk @@ -0,0 +1,42 @@ +# 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 +# yajl +YAJLLIBS = -lyajl +YAJLINC = /usr/include/yajl + +# includes and libs +INCS = -I${X11INC} -I${FREETYPEINC} -I${YAJLINC} +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} ${YAJLLIBS} -lXrender -lImlib2 + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} +LDFLAGS = ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc diff --git a/KleinDwm/source/config.mk.orig b/KleinDwm/source/config.mk.orig new file mode 100644 index 0000000..ef8acf7 --- /dev/null +++ b/KleinDwm/source/config.mk.orig @@ -0,0 +1,39 @@ +# dwm version +VERSION = 6.4 + +# 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_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} +LDFLAGS = ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc diff --git a/KleinDwm/source/config.mk.rej b/KleinDwm/source/config.mk.rej new file mode 100644 index 0000000..15822b6 --- /dev/null +++ b/KleinDwm/source/config.mk.rej @@ -0,0 +1,10 @@ +--- config.mk ++++ config.mk +@@ -20,9 +20,13 @@ FREETYPEINC = /usr/include/freetype2 + # OpenBSD (uncomment) + #FREETYPEINC = ${X11INC}/freetype2 + + # includes and libs + + # flags + CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} diff --git a/KleinDwm/source/drw.c b/KleinDwm/source/drw.c new file mode 100644 index 0000000..74a9fcc --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/drw.c.orig b/KleinDwm/source/drw.c.orig new file mode 100644 index 0000000..a58a2b4 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/drw.h b/KleinDwm/source/drw.h new file mode 100644 index 0000000..e42eead --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/drw.h.orig b/KleinDwm/source/drw.h.orig new file mode 100644 index 0000000..6471431 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/drw.o b/KleinDwm/source/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..2d0276bbc5d85fff0ad1df253382a4a085a81e00 GIT binary patch literal 13728 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 N1}Oc)6hdiq{Q#AN?V11p literal 0 HcmV?d00001 diff --git a/KleinDwm/source/dwm b/KleinDwm/source/dwm new file mode 100755 index 0000000000000000000000000000000000000000..2f00bfcbc78e80e61f67aed5d34939294aa89426 GIT binary patch literal 114296 zcmb<-^>JfjWMqH=W(GS35br}JM8p9?F;r{=i9$FI3>FN$3=Rwm4Dt+Y3=9k`3=9k~ zb?EdBCWsyw%>m&uFhli8fEWx64Cu5BR2__l*$$#X_CcjFsD=QD5R7IJfbc>3SV7DL z5W&E}fJV<<3lWFW$ojzc34lysU|>L_W6UA?U^KEmP}l@`Li7bhqS6LR5PcEa5E`Zr z6kH&E39%6M3Aw0r07wA?0|ShPg(t|3AZ!5*Pjp%VY7LA=*B1fRhfcdd^}%S69U!6L zrzI&MHo849KFl5%4YMx*s&9uP#9!$29B?EtFu-V#9U%JxpO&P6!Ue=821BErAqZ+8 zu6WoW1~CmrLtMqcpr4bOWM-nDlcJlGnO9n&TVY|QYi6QXoUdmD)&O!B$PSQm-Tgut zm>L)kfW%<>L2MBQ25>qD$tx&iZpk^scHH*5i+88qk;=Tbmwq5MATvOEKx#k+gQ5W% z#UL(N{|*KQaGnJDzr%4MNI4fsY&Td11H*;XbGf@7PpWZtz53=xVT6q3r*2rbfvP}I z3hWFFLI^IL^%95pZ5Hh6@33MQufQRG42LVL!r{(^ z9N5(>;xI>&4ZHeu9Ok6ru$K)-_#5H~Hv=60O~nzOvvG*aGB7Z}@+dqpFfcF(FX2cJi*SUS2M&AR;qaF^j&Lx*;m$N1>EQ|v^N->X zzsP_+U3KCJhkhLXio_wl7Dsw`gCpFEafEXp4)w2agu{Ov=70({Z0Ya-j_~Kf5&j}L z%&)-Vo?ST1|AIrk8V>QlIKp`wj(B{IBi?mzLYNN{}D%g-NRvy3J&!%aioWG9PZqUBOD&%h{seM_TIt~&SE&s>BQllV>tZV zh9kW##v$H;!(TsfsQ-sUeGm@+O5t$-MI7R9aDQz_60}0aN{s% z77lxTaQHVAM>reduvZ^P_|)UDmmP=rZXEvIjUzno;js4+j&R_?;olq_={69Df6H)$ z&kh{+a^rAkJq~e29PXTi!+a|o;ns}9oQ*jAdj^L&2XVMF0$2LSq5dxp@wGVowFZa! z<2cg8CLHGU;!vN3BObePgpVtZ^l%GDdalJ`ju8&`u;TDcs z^vvRt)S?hy=bZe!)R4raoK%MR`1IWTy!hgh#G;b;c!*N03L|_I3xZ3N$}{s)^2>`E zBHU9;!oh6El9Hm#q|%bqVupy|)RJ&tkIcLhr~==BqWqHlZLqup^Zejt%DGU+5iN)DaMZu{h!Kpc^$t9WjdH&^j zAf+y;#U(}gl`zG@m3he^EpXlLsU;z)6(tBeor_WvOHuPHAYO*~4&+m?_nq>K zQb1;d+!UNzR0d)@XC&sOr$Vej4jzbcZuxm7&ZR}g`9)A8{ql=)6LX-!22lgi3l31% zqN4mFkHox`9FWnWdFe%oNlv9DCHZ*_5x)6lsX?j5nN?8B7$O{Va`MZ;AyCW^5m=g9 zR0$6#kPE$2D7UemXIJJZ!!oMIj&n2_CASbaB zoZv!=67z~desjw&g2Y@9~0}=r#0>>MO11b?< z!4i=ME(%>MN>cMmibG4%EI=wi%0Xs=!UDtx>41iGL|O@m2Px8F>OleOT9H`{st+J8 z@=YvB&S0oa%*u&RPtA)@$u9-fLU4XDxJE$8Cxhbw!A~qIO00}e24!)CU~XbTe11V{ z9z>+1C^a=c4U|pbrh*ip>46x8rmFxPy>M-Lr8zkeGeJ&GPc4a0DosNOB7u!pInq*P>`AuUkobWQXr~fD)RG6QY%X0 zi&INb#33ev{F#zik{F+sUld=Ing}XjUDj8A>@^fkXn&h!cbh2Si+E& zoL7>=kd&F10#Z|yoKeJ3nwOKAm(7rynwy(nmda3ESzMBu%TS!3oSj<2kd|MR4N{qt zn#TZgG1RK^M6iKH#U(`yx!Eb1MIcFVq@`A*CYKd}v?M2%FcfE$mZapD=P@LfAD@$%lniYk4glx*Rgh$3*R1@XapJwZv* z2+mCgvCJ6aQ^AQMK0c|q7!r64@u_(!4DLRjPR{X0dd9{GrU`YAPi!IWI!0ihuFu;z{J1? z4mpH4LKdP1#0SZ;GGv4M9H3ZWVAwoy8XE%x8$&NtYzvsr$}j`UFOtmU1g+p$4CPB^ zX7Yk&t=B{OrBWbyCWf6{5c6Q`7&stYh6_~?Mg(+y15A7ZR6GGqJY5#jKk7ge&w+{` zKoif0ioZY;Z-R<5L_y4h**gg;Zhip1DZHYy$71O091Vjnm9~-0h+i1RQ&`r zahUoYXyUMZ@c>O6mi{>sAnt|v3zp6e(8P71`8NWGcmtaF257!pfkXTPnm8=|-#`;r zsD!xh2bwrcJwqZy9_D@qsCor7ahQ4yH1P)&5d9u#;xP3AXyON;>I=}sVd^W;#1}x- z&p;E0sb7F5-T+m908JdG{sfwM0#yAAG;x^v4`|{JQ1t>y5P!qM4W?cKP22$*o)&20 zu=JCFCJrk1qR-IIR4Ju}e6VeUDACJuAY3p8<BiJ$0(lvfvUi2pzn zhn2Ss(Dnr^pEf|tTLm<6n0gH~@dr@#9%$k)^#N$&2cYT;(8OWtE6~IjK-D*(iK{9= z!nXrWToWoj0Zm*NDn0{E+z=|h08QK!D!u|u+!89j0ZrT%D!v0v+z~2%08QK#Dt-b@ z+!HDeYbV0u#Sbd}0!_WjWJtWj+KDjrK~VLu_8?3=3@Xk6ZO6gH%jQDNfwc!=;!#lb z5@_nJZ$i|=+Ji9laZvRdXzC52>I2ZkQrXrPJ1%0mk@@f%Qc9MHs>p#6OhG;vsY9)Tu)1!_(Ln)n~6dos|(VdZTF zn)n5%ISpvyY*6=fpozo!7cjQCjJ2`{sK+>8&v!Qn)n~6_zyI3CeVUaP!R|1FT&Cr2UJ`EO`H!Z zu7M`502Mbt6L)}$TcC+YLd9YIRG7W#P;n15_4!co05tI;s5q>DjBZW_ntGTy1!&?^ zq2|E)<>=;gps9zMGXYI}8`K=wcmTRNE6~)#%-Mh@eg$d{Y#ajJoD*p3Vdh*w6K9c! zlq0b54|H>0ps9zM^8rm<4QdW-+y&hn4ru!kCJr-408QK*Y7T6?2c{kt?;2?8Vdfa1 ziDyF1fsI?h%;|=Pn+KZuE~xqdH1YFL^$}>|y-@YAb~?=bc~J2VH1$iM;uFxsmqW#2 z?Q57hFQMYFb~;S_3sn3BnmPZW;up}wnV{`JSpNXsoEK>7Vdi{56JHB0A7JCqFmqtx z2^-IbiL*oP)qsw3!^8!l;s$8q6QJT2XyT$!aR)SU0qA~A4>WN}sQLgjaSo{Z2sCk7 zsQLsn@efe(3^Z{?sCWUII0MxD1~l;(Q1u;X;xPA5KofreRX+nw9HxE&n)nT<`W0y6 zF!dYI#4kYA??4lWsXu@wegdlg1e!QZ{RK4f15ouh(8OWtAE1fvfU19iCJs~o0Zn`Z zRQ(S$ahQ4r=z2z2dRqZi&w(ZmQ?Gy~4ofE*XyP#S25922bYg)f4oeRXXyP#S9%$k} zpzh2-6Ni~ofF=$zrviughI~l54%&k&4ywPQcn6X=WM?o$;sBDkEkp=Roaki@yc zA`l`1Nt_2F1SS)Z#CgFY5F!IfoDU)dCJT_n`N1L(q5?@Av{xG<%D~WoBrXUOWME+E zKoUpZXFUN)To_6H3?y;TesP%80wi%!m>>fK!wMvE=+;@!p39tx+*nuQ22@wL5 z2av?2z#PX@S zNa7ku;uc8ann>afNa9*Z;vPuiu%jVB(g8@~pgp1>F%XVG64wO@Kyd<+xE@ptL}eg} z>w^TKxByAq04fHeDv-ntK>|?RfFy1V6$4QnNa7|S0Vtk;Bo1C61QnlwByI*315pc* z#GyxQfP@)VAc%+w}S|Q z$s0)G_FxeR@c>C2b|wl)<^__tBS-*>KOl)aLB&AS4yOC{Xh~= z1_{7$F(Un^z{EiFvPj~oNa6xW;%P|Y5=i3dNa6}e;u%Qd8c5=qNa6-a;-EcIFewWp z@obnN=)5E(@f;*^4Jb@%$3l@P87m&p3AVOgB29kI^SOh{mKoW0&2!Y8LNaBrP z5eV@CNxTUn1SWqVi8q5qphO8G{kK4cKokd(cq>Q%iUp9wL1#2Tg&8D}#M@zlpzFqAr>Hs&wvPl$rVWAGr=MdVgr)+EQk=8+<_!M8!Q4L4j_rofe3-g z6G-CFsZ+4{1tjr#U=av$14(>7LV6F}|%B@huX$$=ye+8YlR1g%p=5{FJDf~6#o#Fv9bAcO*v_)3TnnAAWLUj-I{ z5C%x%t06*Q(gH~wx+E7Y?tml?J$eo-?tvr@U4jV~4?q%!PW^(#Bap;5fJGog0+RSf zh!B{}KoZ{s7J(22NaD~bTd+h0lK2*|2!v=r65k3D0+Ss`;-Ir{z=ELm50dzHnBXWq z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*Oqai?-5b$Vz!{O1*x|E-h!K3v+ z3Df@z9?eHM4#VyKZ+eQKk>S7U8Gc3vet8Fm|EeH9ctrf=ga7~k|5rW4&j{H}^YQ|i zzYD|%Ej@U70LvClDXB1mNWb zFy9Kq2TlFIJOJh!f%u>)|CbxUd@T?kG}Zrd0hq4@;)AC6Urqq?r9gbp)c(r`FkcA7 z2Q4{ySpep9f%u>){+9`0J`;!!n%aLE0OtSVV`RtxP3gaM0P{bA_@Jr$mj+<|D-a(v zh5u3k%zp&pgQo6Z3V`{yKzz`Y{YwTg{}PA~nyP>K;V;Plr$Btr6#dHwVE!QxA2c=p z@&cH@3&aOa$-g`R=5GS=K~wQBH-P!8Kzz^?{L2Mk{vr?`H1+;+0+>Gw#0O2ezia^W zCxQ5&srHuzV15^f51L|snE>WDf%u@Q^_Kx)eieuhno@u10Ol8g_@Jrumj+;d7Kjg; zLVu|M<|l#ppsDkh0$_d=h!2`Ff5`ym2Z8vYsq&W}{($`N1>%FI$X`AH^PNC^(A4X<`0`Wmp+%FBl{8u18 zXlnbV0+{~@#0M==dMN1mc6Ho?jY(`B@-7 zXv+De0+^o!;)AA|UkZTvQ6N5Oiuol2m>&e)U}8y=HCMGK~uah8NmEYAU3B(6Y-M-ua=C1e z51Mj)sQ~6Df%u@Q)|UcceiVognqqy)0OkjQ_@JrPmmhwB{O<+ggQiqpJ^=HbKzz_t z>dOmYz7>cMnnHbf0L(W6@j+9kFE@bsS|C1X%Jk&|FkcD82Thf}oB-xaf%u>)(w7Zj zz7U8Hni_ps0OoUn_@F7#mkD4#6NnF*3Vj&>=KtbmWXJ$bfxdJA^FM+3psCN724Max z5Fa$<`BDMQe+1%#raE5=fcdvTe9#o` zh!2`_e5nBDCxQ5&sm7NAV15*c51L|p$pGdDf%u@Q#g`wxf&A|U;)A9XUp@fyoj`oh zRN~7EV7?WI51K-Jc>v5e0`Wmphc7pP`C1@8Xv*;A0x(|*#0O0kzMKH&OM&>HDZ-Zx zV7?HD51JZ$Spep9f%u>)!IueOJ`;!!nhJaw0OtSVVr0kwO#!}i0P{bA_@Jr3mj+<| zD-a(v<@Ztn%zp&pgQogk3V`{yKzz^?-%AEC{}PA~n%aB$;Va1hr$Btrl-|n+VE!Qx zA2gNs@&cH@3&aOa;k`Tn=5GS=K~r}xH-P!8Kzz`Y-OB}F{vr?`G*$O<0+>Gw#0O2$ zy=(yUCxQ5&skxU0V15^f4_YGoG6BqQ0`WmpbT0$I{3;M1G&T3q0n9G~@j+8^FAc!_ zED#?w757pB%ufRGK~r!q1;G3$5Fa%4_L2e24+8N)Q*JLmd;$623&aOawY_`*<~xD- zpeeSO7r=Zg5Fa$P_VNIjpTxq*kOoRG;5K!rN9R+=aK|vu&To#Pjv>Jwjc-6lqBs9% zDwXxQa`2U6O z_y7MrI$6Vn!Oi_r9*VE15de*t247YMw_{smUeBFxBO`0d475UqBFF$_k*QE51H*pM3WgW6e}X(}`XhpYVLzyq{h|vb&~3X}2w`RCu};>@ zASWZ+9U#!{A>q>PAmh;~8Yjfa(8;?-n32JylXammBg2anJ_d$epk9VYuc-w$BZEih zage`-7#Uvd;Aa5!b-rgk~;^Hn+}uP4Uy{y$!&tjRm0>q zLFBYSa?>Dk@i4ii5V`$=j0`WTAac$yxw#Oz(;&Gxh@3i1ZYD%-J4nt6BF7Drn+1_u z43blW$h{YU`h5XJt{)`F1(CZ9lbZ;Uvj)k%gT(!Im|PP?4rKp}OAxu)Fu4+loG?h= zHi%q3Os)_j_g{dK!PW5pi|G*Q1ekO-M4AJnwHhMl0+UOI$o&yuWOxw|k<)<5B|+p~ zfaIJZay&4(aERO$keoV1?gKy6`_2$K1&|y!MD7YqP9Gu%GUvtnAK)CbfuE6KAE<@w z(aHLUpON9kMk#Q*n*kCB)%+fvtXn|hGa=#)AaPKS!lRRQ2}rzAih*GtsEfQG)UWpF zWSs^Q{W}Y63aCH33)CO?=)C9IdDNqmH3B3%gAbH!T@`$~bv-~uNVlyfi0ZrtQQLf2 z;zcPR1A{N)U(e>_9G;!744%#Z6-qBa>;d(^eR^&6xEL7>Z@-ufvZ<5x2OlGYPq*$z zK1K$QZd*{l(x>y^F3`|`XY+A^7iu6I8Si>D9|jxe(fr$^^uh}bdr$!(+rr1l@WMoo zf#HR20La;<)qIQ$h9?a#d34*(;A3QP3~<~DDnA@|fXFNc5PYHk>HmL^<|7KOh9`G| zdZZrRqJJQkr-G|9=TX6oW@+YsG(1+1d&k*YM~(=+W7FFs?33brC} zEy~}b!^ps3c-x~J><#O=AUXbKQ2W=h^NvUNR8SCmblZO6W@PZ{HhJ)(`zzQlG0n9d z3{Xv_{va8TZrk(Rj0`@VuU>oyt0}dB$~W)L`TzevlUKLRgHlPTm`C?kkjWsi7fc-t z46nIg*y}Mccr^cDF1hpn@Jo;mkgN8B+z)Y8aW%q4Ufm|oJ-Th%K(2a`4R%Wjv2H1A2fJmnE|OcyK*_H4e+lagyEmW&XnTJ-1H%iWPyhdWb{_F) zJ;2{`oDr1CZTEr1rNH7xL8FgO4HgU~wjQ0wU#PzS4~buo-U8Ma&eK53c}v$YfP%}T z*L2!)Pmc=Q$sy2682%)c`g77(OsC*n+M(EpY?c(ee(| zkh<^DTfp|B;|s`VU>_)f+8MpPr&ohq`pEErN3ZFJWgzzJ7e-(!>Nz|*@4q|*YCUz{ z_vzI=vy6e^g*lRg#b1O@1^afcC)DX^y-4lU%^9L`Ak9sLb@}wtWgN$zFN% zn(kf#PM}LZ{r~^k-y<1R@PNX~rSp|*=L?VQ+dLlKw%eIOC79{vB@7H6j5j>GJy<+C z4;vov>^%7*2UKPXdvx1QW@cmnB?r^PpdLdg$T;r*hdrB*@VuP%4;(-2;9vqrV+prs zx1CStw->oDK^d5pX$B}sg3A^UNcjyK8u#b~2cY2rkIq(!|NsBLP!0oCNvsdpKpyS| zg{4Qg>@7A%hVFm}kItS8pkA9t>+KRbkM7-|9+5}u{}N8aZ!f(485mx__UN7s>ejx# z>(R~I3pT=b9UG{kYhVC%Etp<`>OR|UkWB0U680C4Dhv#--+FYj-T<}lyKNiUK#dQd zUey^OCEW%uCcFmeVBHCkPJ)X5f|#=yR20^|`v2dv+vJW%x2-iu`v2h^F1i zbHm#{-KsWhj0~RLHZq`A$_syx*5=fnoca72N%q0?8_OLA;Z@K-C>M)4s5Qh#v(TB3cJ>M7L=_NWai4Q2u@KrVNx{ z9RDydyqNGDl)J(8*-=JNjN5W7W?*=666{UeCXgmukOO*CZx|kkc8qb1b&PY2k3F2G z$1e{WaA9Cbn_&3Xv-4t{N9QAt<|iKlLR<|G7@qX#wN3r_|Nno{{a+avz93YC=6gJP zZ6m>Uu?B%C%O5489^I^UU%_3d63+h*_~k)kK@5kXv`4S4CP-ngsS=3N{O8g6!QIyZ0C*Yhr}Ldh=V6b|3=WUZ_b#0Y0-gt-u={joNVs$wxO7&q zcyzadde&~;E(#Ve3P6Qa=X;M%X9cLZS9ghm!;2M9K&~lPa0R*dy+`LE!;>$bJOvfW z?>su+8=my&bk;Du^kUxZf zb9f{>3%GPX?0n_XT`Yk#RN&I>VDVxBOcBUK$<6{u@*dp<4lgb|hWN{)yP5;+H;~7; zL3VXIYk-`2_%LYb1T=0E8tl>h1{68`EmELjtMwayiy(;6`P;Meichb}HOIZ6OlJ7j z*YXX2(+36y2G8!WA0DljN{YIR1wb};U@%qNg<2qubUTqTwz?!%t8x*}eySDEqfwFK91H(Sh+3cDAr&p=UC?eGFLr|SXA4aT5&YW)`M(RrDF$^nnoOZ-y~aa<^M0%agu zpKgB+k7RcN&(04Xoi{=8?#bK;O6WeF=RLXu0>II~5L~>K3V3wh^XRp`JduGR`ei~l z14Hy-$1rf5dvsof#QHao4b8t8`KN)b@bc{Z3$n$@v-6ur=lgx2x!D&2J&+Pg*VXWe zPvn+Z-_!DqN4JX#N70SfOoj(Qp#{wl;BaE` z=qz`Dvn&ofhCBDNFnD(U2f4|$^Q>ctYv+;B&a=UtmpvNaoM2#Ju)M(E1Uk&oxtE30 z@&tbq=zwXD-WnAL!vi}(ozBmzJ-gjGI&)NPIzv<}j=QM1fO^E>Q0OdC@c<>UfNqDF z<1Q*0AmyDdDiI#w-YSR(a(RqLuZxNWsG$i?98%9gby&HCXLmhE=SxVRvh&spV{p}0 z&jIc;cOLWTWKnrB{~1VWJx8dk;mKe~4)g3h2I@M4(v*frcZrI^izu*p^&Fs%G^hzM z>j^}Y;kOs-A3&HM-6krKnxpg3i>!wTLC?-(FMhp%@C{G;bbbR_(krStfq~(L(v$!H zJ8u|X0=e_x0|BtK7t?n?&y0Qd|&_y1A`aEFaQ6483xYspE~b%zI$N=O01p7!Tk$sM$q`0`k#p1;>xYj=)bttjt z(wU-S;R1?3Slqo3e+DXizk?j+0g7BmWQM2^h{qT4PeDNii8>cp+_`i<(JN*Sqv{0fU;oo5rr4*uz2cx_u}>=kRA0L zhTmR%xCdc+blwBS3%K4`b001Siagur|Np;cb~XIw(Rc)un0-1wAQc?Y(ic?Cg$4(A zhp0GswEpLB0ZsOJbh}%Cs?62{C1sACUmQEnxpW?N?L6Olsf5j=J3!zyvq!gpM>n+i zIQH=Wf7jOk9^Jv<;>4r7SfNC&Ge^b2wezE6v}@;!XqV21&}`@!|8n`m|NsA=b?x>0 z@7nnSRK4V=I5xM@&L5x>!PukujX>vhpY8w-pUzW`0Y2Rh8m@*X z0}W4x`*dCp+Xos21Eu~fh8I&n@!WjG<1jd{n?Q=edXB&#$3Vxz1J#M1jv=0%XF4x>bbbo;={yQHtoeOhTgz@%1_qz*bO*x&zO7IA zTh_5MFf{K`0h_b~)CJ&oZ2|KPPl9q6C@X`?B#+J#6@|_k6`q%%EklL}U|JkIzq@pv z1r^@i|_Q-Rt-tG=@`N>Dl~~vBc34+%E9wEm6q;>-ISKl+CBpN5#X5f1i!T3-z!6|9fyA z^=Ph9$zUiA^89}sWRQnv^M95SR$t4XrKdc4Sv$KK7`&`?PSlus^s?GOIrSPIy{!xy z|Nnc~{%2=k*zxy21A_;@+l$wtdqGUdVCriQ-_D<4zE5|tgR9|xUr@X12?GOzW9NI< z*4rh4pejPZ)$o8z=P8d~AC(B7&hKF7y#TG1>1GZ44hnc56%QB27nYzp)xR4Q)Qm4W zT~tg!m8^>J zsRvyRZ*!dBZ!rP&k84yU7)tWHeN=c}i#CHe{4JnKLXd!~;eS`dZ!U~EDm*TlIVv1r zXYsdy=5#%~LsSI1T~rJlwOv4swh|SGi!VHwi!GR$!n|L+*#80pelqoPsb-wnxR;70uY0FPeQhaC(I;B4X0?W5w+9in2>U83Uh z;{5Ob|Dz&7sY8K*zvVM1#epn&vHbS`|1X~~g2uQ(E(NEo1`7uMmUWB_42})Y8Tnh6 zfMsv{wtfS3@LW^`d^^8*bV3sMOEJ)lU@vRIXHdI%0s|8RgNLOCe+%enZI9*y93GYz zO5eShbnE~B&UcQW{>pJsRr2XNsEO$8;L%(m!B7gCyVUOhH>i6WLB*7_gGYCT#EUgI zKvm}_k6zP&4h9DBe8lk=+aJMtF1@CX9iZW}_b;69{r_)x$*1$3;r|!z4?vpUzqtS4 z|9@y}-ly{wC_%=&@CK!y&hNgR&p;Vb;>GTJ5Vg%<=ai^Oc(i^i<%Y!gOCeC(x4W9- z#Zpi;2p*g)mGI~`P5T7$sqMLTP>_9lk$&g@|JMG1C+vh11dba9V$G) zLldnBN(4N5Cv<=$x*I^$>t`O_?I4CnC(~h%?&)AX-A&-(r`|3wg`~4%0z@Z>0_y}Z zJUSUYEO&v1IZBN@pjulGl*oH@cYsXmE)41R*%+hT5yHs8P|E4i-2l?``i@6u6G+ct zk8W18pP(ckzyS*WOC`eHT>@ZhCV|}5>%zhG`USY7-g=;fxi^5R^8%y+bD%_{c?YQb zzyRuZLwxhnqk96_Qm>Hikggcz2_Q>L9v=2+wr42eI?lS`GXrSsgu(DYCo9J*28P3q zou_=dWqm(0Fa*2w@`!tM^Ll<}U@*Mx$oR>l^<+szcc4SJqlZhkqYL8&7t0fl{Qd_m zfARZV*SxTc0W@Iiai}}c$A$5bi{&Lpe*a63mLFXBeU7%~5uH=+-Fbc@~wWu?|jB`O}>P8Fa*ejyLb9Say47(nAda6e4~ zYr1ZD;4mnN{+B3ubh9%400o)@sApaR*8~b)P^i6r2}+(G-K@o6^Z%EydsrSXVKO`b z>A^O?N$}|24zdQ+p9d-F1$CNxLsSG_bY1~v$Wl&^<1Q)!pjlFIc?imyod=sA2{b+g z1z-s~sHzKi%>^1V-~+KWJUTanS`V+eTn!KSbT)(7D;!F|auyz)n++Hl7+!N4{`ctI z4PvfPfJhm5bZ)kQNPUAcK}NEJN^K30@h3s*LR11gIww0o)LnuyS2UDpedgElQE~Xp zujQiR@|i#GAeis-nP0FO8wWA4vf9yTQv;z~fq;ooB#p*=|Abyep`! z56OV7m-ziII(CYvICcww*{-c89XkcO`+Y!>oZHPR`iX(TlC6%ndpao9_lkIFUg%`& zX8rY%fx*$zm7z|wyBnmmd$I^40|QvvvD1~IdooB6G|cbF%+|{i(0ZVf)$o#Irx-+{ z+m*qQ*_8nzQD%4mq~ cYp_TfPX{uo`Jj#qaDm(CVR@mR#j!J#p|?j%SeC&-?-_!;x z;}1UovJ0+~U;jo~I+Dt=sLzi4T0APB`L$db_%$AW=8t>8um7k_>N9`jq0jtL2R`vf zum((IVEDwZ12*Zk0w^`{>mM!?|I8nG`ZIsj$xr+dyt)u22SI%k%ZvOi;s5{t@9qYr z^VX9kf*`-l{QeU}XpTc2L*ojZVpBU2Jk3>qorM` zdiQP~Q0vCBmZ4O-dp9WQIa=DNl=62sgJRy&m4SaMDCNT{$HU+*6lk0;xSKWVJ*bBI zZF#Lk&hkQu%x8YBW1so8PJx2vGr!h}&-{@GKlAH+c+J##taCHSXi(FvcQVLAkKS&O zKquH1kKWB7NBQ(Zc%AoOto-x;KP07hvwnHUz+ic(&Y&A?MCU=vPbCr%Ye3e4^E%9` z6P-w=Mjt-x(flUEqkB5YaL}>^*4DS6vJzb2b%L7Zy)G&oI~f?57(l~G79NnScicsV z1GKRKB<9dP9h42Aqeu#!Au1lAp@tBZ01#E8q50V`Dvy4yi^c5Vlg zC7>oP#L{-qDz@%+PzZER2b1lflj^(M!GX~aCfgN2aoi3HewR)#)!D8B76Ln^+jjpO z28PZM6`oz7?FOAbDgvM;9S5kB3mO5EcrpJRsDW_*#lL@`rlswKH{gjO%PU|v8bpIa zMWeGsMF!M=k^%R{dP7tsI(<|`Kn96`3=#nKz6CrwYg9O1$bwg4)u?c^9w-q5butCI zeN;rcLsTTXOH^cDaDtm#M>ODr&aQ?}LZLly36JjjiWgx&Koh^mK((5nNB0g;z2x}+ z$P4=)|Np;!}~_aN4IxCFH1nLh$iD9>tLVaWX2PWM_?LRch~xh=rP&_ctg~K$lJpKx6RLSX_!P*JkWc(t9yXu&1w`le zfQl?F2Mf@6EhG!`Yup4)cYWfIyv48a;1hr3qfajUI)^{;$DIDeuXE-Tf6S3j{E??V z@kbu|#IJMl6Ti-fmujG5x%r3$!~~FbNJ4MD&EEpL=+2|N8Qj=uDVtuHfSlDm8`O*hZ+Zevmx4w#K^vZ+)n79xN1-^OS0uor+qUK< z14DH7rGGCkpZfnl`fz|pukGq;aJNgegn^;?#Q&(np@wfksW=EU$YPx>Q7qxn?JUr2 zvw{`WcC~r`BJ=G3{~rI3zP|q=9>nUd7I+Z`W>`l=$G-GF`~QECOSikni~4W>|9e;- zFXb{k@IvD2|NoAf2fF=ry2BknEiVQJ2A6JLA#fbpK7YZ$5b?6`-2eZf9-TiyQ#TeK z-SrwT?t+Fhx(g$~W47BtP2$eO4&ROml<;*sI&?OJ(&x+jpd2&-6mYGVN@YE|S({!k zFmzXd!uAJvJ{c6Y{H>tlb3M9ugJUb-z~S35#uD-FW>7qJHiL^MM+3$eonQf%Zg&%h zZ^xKQB)XeHQP|lGE~y+%To?~@f<+v@9b+jG?FNT&XEV6ea!oRVCM-Cqw}DP=E+_bO^7?Y{cRxW0aBAW^@=bu9=E(+rw1}e6Kc**(BuKcoS&V? z!N&aTJgE88sh5Qj?iuD1K8J>9JdmcyK~VP&Te_}*BqvDfhNf#5NIS^|;x~_OTbbt! z3@?_S`Tzg5r$^`g7bTz-#`fKg4j$cs2B0Aqk8Vc~Q2oN;(H#gLCsXKl3;>nB93WAF z&QBhl|2(>FuRdd7=yXxhcyZ5VI===x26Trhq&5EH(BhwR zAg%Eirl$cK1Wj_{b~J6r*W4NWAI-I7AnN#UFvi^+DoE7ykc$ z?E&ha90k>n65t*us3KJG=w*Fb3u^GZ_W(uV3l30jZ#__|?9pu+1F?}2WI_VS#?JRI zZk+i4zxl-fmy5w;Mo*e87)x1vx)Z>wj&e?d7K~IdzIc2bG_vsw;^7yrXaD~<1kKLv z0?juB{&36#|{r60QGBP4|O( zY%f}m|Np-qVw5unXtj08anSIP;mPC9pxn;j(djJEDd5@3qw*45jduP44?Tc}|2m() z2tWJ(zgMpfXww0MtKk!mPG^o6#>YWP%UQt1x>|z2c^`Pf*p0)#S4YLGn?(a$LV-3( zbaOQSWGeOWVb13G=Agh>qUzBN?xBJf#DdDvN-n44EDE4Z`MMCq0u}hL(?9V?IS71q zgCa0z~RyP9TX@WFLc29!JEUQw}R1!@uf#6kIIV~pcK$8 zq5|rLa(Hwy!HfVe?(KHw@aneFc=7o3|NoBvk9#&BVR>l*YPod2f1wWwlmi}(M?eV* zsg4WX2b$XfkH~m{r^!LC3=Vv8I}el#A>!aw3Nao#&Yt112T6lhA@s7YIS)!G&EO2# zd_cgXvzTKasGsA}nO$()IRLbt%cHXyH0j`RoH;tymf=NP9s|Q?egS6%pKh==Q2VkO zG~DUYy&J5qQ^Td(Tcx`=qT9Q`rPD`%<1@dYi-3VoXUhaosHNm{=!ZwIXj>iFzh4i5vQllui_M_53Z0;_wi3N= zaE;pS>*3LP-_`J>OSh{}cc=^Ff$mZV&4Z4b2VU&?0@Ae`)GT{_G$J|{)S#B|?EK-` z`O%}>RuNnjgs5-?cyyb-0cFz8i=e4!o&XRj5D@Uf{}i|-ZUJdTfo8lcZ4Wp#-km;=lK*K&WK*3p} z?$O-<3cA-XLAB0Ou%`}rbhDO%6|P01y3uQuXK69=)tniy0U|A`vVN3=E)ocaRKd!W>i_a(I9mgP^qv9?iQ! zZeieW1>KqK(Rtsc+u7qqCCDw7hrz8Np*P@GkVm)e*ZT|%9?7h74;dJ`y>+^S9gt?L zLNge-z^$V%4;UE!7dUuyo6ft>z>vkz?O+j*#qh%NFenymH-U_3K4Rd}*$lE4G!5L% zx)&taE%2fhR7`Z%}LaWMEqbR$}nN2^1Od)R=Ogf#HQdicla( zVdtY4JHcrMy!P%TKd9trJOT=|=-9)KA%+LQD{a7Y?i?PimrB_U4}jO%9%EwYJnPe& zq9Wh{nuZ3|>5O3WF8g#odeL?m9LJYIQxO^rC8z(N1+gULONBsXn1Dy;D^N5FocRC$ zwW~+B?eTk{65mIK1vJ^u;nVr-Md%@r0Tmj}H7YC&b&5XSDJmSE-6kraF=1hk&ifvn zuU=dP*D%jtSRViX{}_ul187KG;Kk|v|Nnyr0X&+I7=X&W!=S}XUqCr7xN|$`wqsE9 z6x7!2w)MZq!0>|WD7bztu>nm`gG#*%4Nv~LwmvCg^XRHlDu)wB-cz7+*QoF)l<>T0-TVK);q8}O89@y( zmgCK*{(E%(F#P7*>+!?K@j{~&;Rxe4CPmRdIJPNjR0FP_o!p%5#P?kF8u2s z`f6VGJoua0*YZxijO7V_r-RKugv)rEUx-*-C@ssSJzI951Tt0ZVtJuR!tj#g_fL+U2Ve6+m4P$fYc|InAWbfvH(WYh z8C(r-yL56p@~=PSXnDBU%d1=FUgzD;TaKN#8Xq$-F#JFGR-*CIe}?}Ij*X8P{xdM} zufOWTzy9jMR~(K9UvZ#Fxo}?Myyes>GSBn>;ny2Ix^3HTffiOidXaVuRAknuD0p-p zesOX)IIUTL(i(UT$zjKE!;_9-j^GwJcpZh|TTtC=>wk-Zp_4cAHUq!zYfHpLR1lS{^Aj;$MFhwAOB4J!rzb+g1i-Bjbb411?=QOfUKl|Nrl3alDk> zrBhevHUop>|0A!D|3BLNm!ouR1ZY_t*nZHs!pqHj{)5+yfl_d1JICa(5B>k&a-fu{^M?Rlmu=;Q9X;B<4N3 zdEeh)V1TS6eeved|Nn9Ep3VPQN>9G-2j%7$oV)-3Z?4v0EM<#+$@L#J3gH+AP8gsy z65kv{L7szkcY0-|w}Gwl?vH*5b*P<8vj(bz{Np!o#T_X~}1e-tmeZSK9;ZfTbV(bwDnu5vd3{&X^?Y4H8ym)5NMM3 z<@CRxwN>9rR==K(677&)Cb*93{0v$c_2TItXqxlr?giCvhTlL@16t2|9hUAoK?%;{ z25J;=fGX$PrTUILc^E*UZ~&Y}4ZlH+@#wbwb`8{?fiA78JOIv*3Ld?(>o$SQinl7du-J-tN7#LpbcyzP6TmuF7iBdt( zydR`(32NJae9ey}V0ohSqeth3!`pij%YJZe>C6INQ1BM440H~ItK}K~KJW%P z{{0q?{QEta9QpSLusHJXcVKh0d|4}ES!YnnZ|P@J%4Hd5QOauRW>ccoS?2&*!QEMB z;L-WrrPEKN)6b>T&!jWVqchB+)6J*T&E`cicp?Do1jqjeJ(~~kyzcO^(kBZeg%|lPx^v-7(Sf`JUb6U!y23fJi99^KnoYP zK^G1{+~L{jSn*;HXqia2LxoQ#WbHvGcx8cScLQi0foEq$g-2&QXw=rTa|cM|^%hVw zS8@j^L%uEbHazeWbdH2av+dE#3=9mVJTHnrfC@9#?U%tV_YIdB7`jEDU14D8X1#NT zfx$8y6dLX}CC56$JvzfJI^BIb-ECgvfk%f~n;@E!K~yJe&SeIM7pldKv1>8LVt%#82f+`RM`7I38|A6k>c@=%wqnCB_Wk_!tG_DPqfzM*x2^y(^tW5Uk z7EJ^h&~1C^5(7iPi#dD#|3BUgx=b3>>57Po2KOCbT-^i8DW#yk;HpcImE0WN6#_4M zxBUPA!eR@^Ma&-E79O3)UYvsRdTlSJGB8AYbQ>5Rc&QN!S~m`g#e&ezuZEyy>AxKV zUi<*>J!ee^83`Y__XP78z%A#!+y4J=eOqGg(QV2F7Pi&C#K2$(*|GDYe<#Rd(HmeH z)0Y>)u?A8X86EpFX&aK>aUJT!Pxx5+2DeDk6@Z z$3gQ~9=!qoJ*;z%|0rSg=)CayC1gR|{}Z4k)i1Vf0wux!C5Jq^byPf(eN=cr(_jjq zp_qH1C5+%fm2O*mkj|xxIex){1>!@3cPA&E_j&j zhetPf*%2rm3wd<+gS2{BdMK2zz69;wKI|Cg(QA4OWJf4?wN>LA@JK*yLE;Z{zkiz4Tq6s|m(xL)tk%7tqk8TE!ZU+HS+KTY#X8jH- zF~Hm44PN}-`u~3xBe=^73RjO_R!gvFMYYZ|Fzf`K8|cw(TXqf{9jyC68oLc%n1Q_4 z>!aeZi-`;Dm!)6{TR)H!K=R;Jz}gQHwSbB;fU`SmC0NvS*Evu@;L&X>36>Ukad9gs z4Vo?jb8VT=F)+Ny+xh>$W1MH_L6EB*FEcRsSaXzGI0pH2vsQz(?*h-scbkerG%Y&| zYA3xo1)hkuJ#ZGBnlrcl|L+(b{aVkX+w|^9P!quxRO7#RxEW-h?W;=+3@?su{Qut- zv}2=OfUP1F$s4^w8fk3=A(i zHvRwa!n_zXL+N3;8Z<0d!tT)tW_WbZ?qFnKc$xJ7|9{95+}0;0td6}gDoh^Tto)ZC zDNOUOW9JQ*Zr;mhp$W_2g~_J>|GQmvATvqbwo}hAFzf;y$^}}54O;fd)6Kf{ECWMl zjEVrL#4|hqnqBhfZUtwEP8SsgQ27cf-ntE5M63e^Bi)&yFw&zcSBfMfWxM*lR0&*;@(L6<(l5Dt}7=I|GAbxZyWwn;%^HG{5n9 zapuPV|DB*Uu$IS5xeX7zNWA|4KWNIwqnp(lnU|NrmOo4^QaJic5DYF&VqJGUMHAF?wm zj)CFDv`|ow?wwEPLr~Sl0Upu&yu{V;zu{ZQ=Cl7D{~z^D{_5PtV$yP;B+;|ikI^Qs zG$hTn^?#|Gf3J>-N4M?FQ=rD&Gf2hec+3@aHptcg|3R!c*WNrv#-pCeM;#k%Skg*2 zy7uNVc_#n$VSN7L)Q1268IQi6=gD{mw1j+;XXm-s)4>J{dUV^mKn#8H_zFl>x3h;! zZz_|E<^xcYF+A|X;oATI-8rDZu$4N+z@T~5qnlOp6ewm36ka?#310CG9>8`1`HPhg zEM@y1lxsoTUED4B1(^5+8TbXf1Pm|n3wj7RhG#K?mYS%%SbygK|6W%{kVO$u9^E-A z9EJzFLRcAEF7dZq2j%Bp55|UHar~`^7#SFjxq$X%blXk^ncVr#vGd3a!z=&)gS$qJ zCqbJCL&2uH8lLPt2-?Qw*m?BDfwTYrgI1+0fNEk;pN0>LG$RK!o)ZEk5_MvEyIhD^`J4TBdJ7yFL?|Njzn@~lTU>(LXSFc$FacD3La5aJgU;1`Gz;1`S#cnLY=;iu>S<4z4V zwhTI@pPCOc`6T~@SbKLJI6-UpbpAqCafrz$`4>dRZjg$Dz9{mpy;V$|J}Nq%$^ShV z-@ll#?*D&CpWdhQy<_KrZWk2+$Dk~RZXXq!?hq9V$6gbb7avZ6N@>TK*CHU-GeNb+ zuKWL=(M3hTwe!!*eo*J6H%FB5r80=sdE%uki0-z%a2%W!UgTeb#^C5ns%^`2RmMtKe>8@ zL^SUP`HR7$TXYK8Cfm}Zpffh!fF>m^Wh0IZWwD8gG)E7KggZEJOVDA&jLNVZAFfPBKM`? zr58z{DUfb(z&b`bM*DPkfb{#eo-DC&>29b1E!*h;8MF@+K;5j&$3W*8FoGh`Mf3Rg ze;40_syEvsN5Da^@WK-`qXjX}quX}v5e5d>+IN4K?herEWKd)|27nypq5_U0r(P3K zG`&{$=r(#0c?6be4KKNLK6B}2oeBw(q$3Orfi9gdvlw6490$cGXxa|60SUB!0Xl>0 z(#@L=QEhSrv?=b0hoj~JSWWC0|MJE0|NjqzR%iV7XnX_OUG`!M=-?=*b3t92rwnih zmzs13M|gBkxeRK}dNfyYFo5Q3z>|2e4QQZANDhzg*`PrJP$O62wU-B^tK!jZyYnyu z!wZvT|NnP8gH|~mh8R=9!BD!>quX}&VNjdFqc=vy;D!C_|Nmcy!IqWrfeZwtiq_ku zuU}umx_0dj$ipBt-L^r8L8Z{kU7({}U$`y>H7CIR8Hjr}dvx0xfK}WA_XS=|UJ34z zzwl^00tx`oGlh8>MrSvQQib96-v09 zcYzk0F?jI19D+{2zdjEd7cvA*)RUykxAUOkCBtu^ECHIhES2)@o(S@*r{yV6ewUA) zmWMp~T`u}|UVMEAWcEW)7-V@^o+wH6>Fxw6^yvogWbw2-=)>=F($n&k55LO=pH8qL zKAjyPC7{th9uLb8pcZx;SitgP2@l9Pc8|_3kbsZn!BRGlPWOt}oIaKZN?ANQ{VTvn zgCF+jW;HzsDxkoNEJ2;jnCAUp&zEpE?+0Z$h7z-&Zt&t=SHqLX+d*=m?pZf@g>N@_ z5wJ@)cqQ+PhAW^HWc%a*14HC%e!~NuVAUSowwDetFo2H+hiq#3Uy|j~ZM*jX14DN> zD3@G^ByUhI3H;1209q=-dp(JPfnWbb8T)7c2zQ0oET8#f4uV#Fu1^9jC5bwb#;<>| zTse(jpLb~z14Fr_M|V4DK}oj&XywQRgU|ee?g}qsLB|<*bpG_{w)H;%^5t}p8joam z6%S@caIxsY?{To3tJ9^6qtjjGr7mb#4LqZ0D}MmAh{B^AtOMMlo^qg@tFxs`fsuis z)4kxuYtYcaH_&#ZZq`rx85o)?3K&Ycx>=PDGBA9<@mkdIQgcNKcqwx!Z?}VuHY?{r za6_5LqnlL=RM7PLsBl0#UbZLqgIs6u;^0DX2rdF~n~zw$ob%`Zf6$2BOprk9?Go^S zn+$k~24pl2G~m|x3sfrSft6f>hC_+AN4F3t2`G2_M|fCIIS&fr5}j@XkLF_{-98&Q znm;mj`fT9n^pAKg*3IG3d{Cr2q=TdR6Juvc2S;ak#B0fJj_&dZ55|)oy*_*b9-Sg5 zJUXX*2N{D{mDd7Ym1pq6W5xgf9?jrH1UhH4ge?kuGf_UCYufi?_^+MVAuybGTx(Cb~h+!x@~Rtfl9ku zFEsCii!pwWUQ@0hkkI`XE0=-8U27i$!;8J2{{N4Dxde3ZO7j~D!vh}OqFVbwo$8Yw zohB-fwTqJbV1qiKqLRa-^I{egXwI1<;KjM+ppZDC0a*&*(f9_`VfN_uSMcaO|KdGp z^+hKr*QtXFR%;Ia)`d)b z*`fkEQm7lk18x33-V7@qJUTan7U4lw&O(;Wc0-hVbb{A8Ll(|LSIxFSjNJ`d?d#Eb zzq2_2yb~z|x)bSz&Z+bV>Ghr_q(BHgO=mLRHuSf z+eYsOwI5!vfCI(WbT_CMed+)I7aLxKLJ90T@I3j8-+%u9e+fEQ8@vE&54e_f)$r&R zUAP-Gb#|e&1k_Ihvt1oZxIC;~J@{LvLX=r_cY}uXJ^r8YX#T-iF4}ssv<@XjUIv}@ zaU;e)R|36d|w6GX7p8W}Orl&{q8wC$b)9E0CdO`b5 zKnq>KF%sy(?{W;1-!*rEL(}j!Xz)oOFc3t^fJqC`u+EEq5P22U$UW@QEvmBQ4i7)zPD7ZJkdL1NY>8?>)6WkqcVJ#}X zlYybz-KLbol9gvC14D^$rzmJwRwwJf9SjUF?mPsa&VBVEsGKcv?-qTsgMp#j^cmPn z+tu3{7+z$9rn~gOJJ)3=feeQ=1FFEqAG8@@`2U5~KTy48^8{Q0I)*@x?gFjk^66d< zYUaK;d+7iF7k@xR?Jdw87k?{gB&8c%+JeTxwu2ie5-*}>f@U^s=O6k1|AjoFL9|iPWRp_Wd?cSp!}j@ZvJqEO7Z^`0d3! zaDdsK11oq@4mQ%(_`?7HFIF!AnP9tR8)&^ED5yK{zqmdNe0F#!sD;)oS_le=Zqux7 z;5g|9DSS2yq)9d(B-DDlB*>#Xy8?8aii1aQE66RNIT}&R?F_D9{Dl_H1{e4RVs<-@SWJo!0=-70?;6?*0cZr zBMygobo*<7nhbkDOEX(R>p?n?gOh*cOL>qX*FoaovyMuZ_!?0*L%EB1up{vsrt1ER0|v~(FVCn&7+$&aSHRrTFF`KeQew(F2{>*UicWcFhgYwtpm# z%!HZ!dNTvVi_MVv<@=jK0aB~rVZ8%1q+e?9YWT$Pwok9A-x>x6?{1T49^JO5LF#k& zfku0B9hh04Y++zvJnU$Bm%j;g{kG@-BcQ>i(n~LQGcYi`*mB_if5$!G_5P4p*LvXr zw#v3-GXsO;9#9+NMGUB;-ECX78I(=Iqb@J@f#SJW*1R9Q#tKwHx>zcg+Iw_+3xGEN zn{~T}SUPi*>UFb*Y++#N4rVb9g)W@Bu;Op2xNGYZP`yG1+`yZlD1eaua9(FV&$t!sCU@-v9qWCv3b}zwiHlX47q(85lG_YaVv7 zeC#Uu#IgAZ^GnhHkls=@$c*OQGeF~BB`)3kpf>zu5D(NL;mB<-h2s8ZO2^M^@3R1pZ+sMG+81eGbyZ`?|N%ir1CJ)AM-ToXd-R=>e`32k!Iz3c;IuH8x z`WOf_e`NIQ<~ilk=@8-3TfztGEITN8bc=lQ>+CrI?%Qn#^=~~or9bULwgmm>v7 zfr?M(G2dPv4gpX&G=BpJ$!C5+2L+E_A436;Zjn>4;PB|2@&ObO-QZw>1qbM~X~X|7 zPJ%W+cH4ek4+@S?Zy>&Uz8*O^_*+haD#&JghB|$Z?p>f7#RGK2Ru4!Nau7*3^c0Wj zppYs}_UPUZQuX=<=s*+0{~nfyN)$mGP(Wj;(48URLqX2IR|FD8Ho{XlXr*Fg$mq})^u9to5^54yDEfAq^KUqQJEq;~H*28LkJ zsleUf7L-T#6zE1-&^S;6=#1cK=&)vKkVkh##f#UV8G6gZr6L~P;Qc8tZh-{A{oxV= zP>1j>c+wi$s|~1tb!R&xDqhHfx0!|2YzBpjN4IN*M`trAvOKz5K}}GP&RCEL zq)|E-B=Ta}m;e94i|ubgQpZWqB_G}J%wpovZMqL6)VvQ8oXXwypu(yT#IrnJ#{(*7 zz=hR0(5{#3FFpQ4%xPQ;I%EsHz6G>`P1U2DwG35yaw8ScRb3Y^Vmz! zrVj9`BydFs+7xpGoCIyn!6q#S6>8w)-Ci>>FuV{2_Y+y8*Mh1M*X}hcpm7A(?ky^y zIleGRo`aM>puU>v<24KnJ3-f}{RhqD{9OZ@;&@>;0n)u`J;2{OjR|ykX^RS|fA0!8 zYUr(J_ZAgUyDZS~q))Hyj+r18Q&d2EYC+@QYZw?95B)zC|Lt3?1AHv6 z*B3Z8?@<8_sxXwMf(CVz9J{B0W5Ka|3piFfU;1=Dhs4MWhW-ElzfS9X<)}Rc9B7>v zzTe>YIMn<@p2^yDN^T?Ct@l6p*o?)By?%P||el-UAMl7hIsV zS(dC(AoDs8mq>yRvhO_hg7eS+|DYwavW}}57+!FKZqjHx0?G)WE-+|@+%XC{(y`Y>p!2Fr=XuTV2-Wp$u9^p3I?r|oxO86EJdcp5Ghhzzfu6k6c>!`N zO0P|kOXs0p6MoH4o!3D2ed;{b{7=4~)ur>0=C5v#2xzt7(cJ=WrGOTll}3Z=H+jgZ z3eYnYI^TJ8-iKz5r@R0Ee;wKRQX9M}zw-nrV;lr!43W-@+Tc?h5Mp(PhW|lFki5A6 z?Einz`4=ADtO6?-7`inD8i!QimD|K+~PW3Rn zWcb##yVS?EH|jq)ZG)W&-X+R-z~S3b#u7f4&W{&gTAnD9c?oI?xpY1R@%WoT_sO|f z-Yqt8>^$?D1H@8u>^#@`viTu1Xn!;pXx)y93*)ob`prL?OTk=y7sgMo^}$L?`CUQR zgMb8-T{J(vmIIxA$q&-V3KFx2%07Fo=A!xRwL$CKQd!65UyLP!pu>z@Jw7|~N4kQK zPI=(i9qRI$zxgj?DVJk+t%r-|bH~m<|Bt$KK6C{gH4WP43f|h<8KT1C(isCf_A^9< zqtis?#RYJC0n(P}11(tV2FZd>0`Cq{VF7J5>^%JPIVeI=&j$fF7G73>@49$d`0xM! z;LhLsLD#{)_}Bqmr)Buf@RH%lP6?0B0Fh1)na&6akIn)GkIn)OkIn)XkIn)f&>By6 zP@-k==rlhJ-f;ju70{!zNWr5sOyjUecbJApx0?c}+rr|}=>$47u!O~G1r;!cGwV45YF1&q`UwBk2X9I z>lgz%mKz)zpk(+Jw9E1e^k|V6&pQ5t7TecJcE0rNE_ut~()r1w^Zw;W9{-QNi2eWn z|LaE}%L{s8MuCdo!;axTy|!B|K<6R&^qThAGcY)Y`t<5nfl-5F3>$49=)avAT$4Vf<&Q1UZ69CeBjC;cMzFQ z1%>c6urg?4v_^%)Be_81g(6%jI6it!b3yj51}g;_HL(X|c8v;$Pv^T`?4WLcKU}U9 zCI=n>0i7&g43bL*Pp3c}d{w( z67W*m&UY`kAWA?rI7CS#Ovwv=kQ6v1L8lpb^qSr=V_-mp_ly0YGy|50IO(5HukAIE zl5L5(zh4H=qY&~Y!b zL2}99GYTOdc?EMGI8HozP0K+>lt7e$=1L*T4#Skas02xY{R=z%}{{IIjZlBJ7p!(}WGw7)5R0hzh26mrb)jN8i1j6DF7!C zumM#d1+q{D;6p15K-s_Z{tFF|VDk|H!voQfN?O1W6ka~Privg2sFJ>C&cFa^0lKMUXr+LGeN5W1B$x6hLhjpI%jW1JM109smDB&XMXh{Q|mtw)6fAbC4;W z_g~0^oCvL&KqktA#FI|NpSoRU_1rISrufTAI=wngenV+h0V^$$%=U1S#pf z{~{5TEPGAG^cfgnE!-EeAi3^VP-orn8)!m^9X8F@dH=+c`0b%4sZ=iC;P`PMuN%Id6vH09rtMJsn4xm7K`8?y2aWyLb)eV>9d_W+z2zvVllZ~{ zlt7SDzzMjL<`0Z;RYIWX>%9NsR2|5n_g@^ULxkxBT}bToLFLLomLg@wGLU$3sSe01 zr$B*&C^$!n z<_p@(3qAwe7j)GV_)@fh7mq>7RT$zm2dHiLK{4NX|HW~T9N25%_-FzJ_+gM7#A^~r z4wZyzT~ZCopzmMkRDoz%w(YC_{~yx+0vq&Qi-F-q6<8&>Za5DLLTDI0*MbCZG+6ri zi^r89gFrPQC}+E(8gvw7kRezlxB_c~W^Ox>BRlWEFsnui2hipN=pjnbL51hF|Nrj- z9kAumYuX02_i+^@T2F)Igh1f}c5)NQrsGxr|9d8X0mYgO$jJ!lGLZBNkXA_0{DtPR ztstS!`!Cj3!Lx^Bm}loTP$D}E+Ejf6np<8(gY1L4#Tx3CaHwv3kQ^c&erYl=ys!qz zK`fg9iY9QD0y|X#C@2`gZUaj%14$=>r5Qk>YzC4BH;us36F|~VU}8Vn3C8bNZ9n7alF9YoB9bgRdh#|q|NJeWmXuHDx6b3cK=yYYeh~yJ zTe?lTA*#jHL3f;#Zu=M>8*L`tjpRM7!HFErcLQ*V0gLw)&Ku5-h$RxwjO|-5)VF75abci z>?vp&G$cJ-1*L<*S4XL zf#F3pNH651KTuE{b_@Yq3t48`{6@f~TOD*2`<((%hwAwY5s+JswjL<8>bBj|3fcqq z2(%8C1+3}Cw-Qh)23^~z0y5hHWOjEo#|yb)kof%59fZT(4b^OVSrxo` z{S+v9H6IaxTv2)05wxG-9w?E3FZoUV|NsB%x80&Ft)T5=R;>&SU>iMpc@On6FubS* zCGkt3OKdwuXM!~Kik<@9sre0jStn~Bh<(7LlXq$_0|PiCd|uUQI-wVoR$jF*fbL5F zf1Gs*l;zRI$hUvp6ae$jI#&0k775x7XQPO&#)Z4Z7tw*=% zuTF6GxuXK2WI zf<`Sqg4KZ%v*{)kQ2P*MGH7p-4M-*#RJ3-xZt&=~c+p+}QkA;n zwN>*E?vmLc(?P-oFkvA_#v`v=pxe?tx>I+Qn!Q+D40TkA7f6$9cjy|AZd+Th-KMsX z(D)Au-_HLpHv9n}ko;P;n;CpGOQ-7rkM7zHpfUM3lA!X|^*|@H;epqzJN|(RncmbJ zNVhdZFIfzM&ZRg8xOC?W_<$EN+51?&Ew=FKeC63|qUX~2-$V1&i`8YI1AdNxg0EEK z#or=`E4fQqJv-mL7HR&$QgZ5bz6ayc*M~hj-*~jXtw`#22H&^_nsu^#QEF;Ai-507e8FA#|Nm<_%~u}1Ci@C8A zIQr5WV9_rg~Oxs8E9J!$BX&k1mdCsx~lZfA)K2iP!=&Sx+DLFWAjos`fm`x=yzT5p#q zyL3KKb_UlNJm8MkK4|Cl#a7T{WOuZ}>l>hsqcVy$;h-z1zkv!Q&>`ZDZytaf;65rI zKAkQq0Y0ERL0S%!Sn+GRs3h=f`luA}Yfb_0rQz3{qXMd``8AiQfI7VVnrpxp8uDvy z0pCf-uenF%0;mB2Iy)D%%?IpRk6y^i9G}jg$6Zt;z}t&K2N{5tnh=Nvt zYP^sJ`7!y0$N!@sSD(!WEgr7{of>=i#jQo4Y#0KX@cIclL>+3VN9TP)h8_cD|8CGh zzMc0##%6p2)l#3pLvHV1fbQ!9UFGA^c@wmt%63KzsDLm%sldSSA_iI`wd8>Jgn+^c zoKoXJCl&Sbf-c(KCCb3S@Y4P7|No)Eoxj0{jP};3fG_k1$BSaHnI#|>`gXnmT^PyX)A`V&^A+f}4hhgYnC2r2KAqq9 zfmRWN10&Wk?xhpRZ16?`a2N=H4CHvB4mK-Ag`@KxNdNN}PaygQd^(@+1NBEh`d@-Z z?LceIKw|-*@nEnCFt7TkaJ+c10P4-odp?~HeLy!ug5C4}g&61vv*sgUe}WqLAoCn! zUvBvW%AOvb{~+y?#y6mYCp*b&p73cr} z6lw6@z!!4M|Nn2WW+(;i1(bvJKfq?`se^gAgDP9 zo&)Z#)$r)N30jH63o6LF*MipWy*}#Ecm(7SklUd<0U_%%6kfXT@D0ZSpzCgQm6m_{}Q~A)Y|lYD+2?6>t0aahHNi*p$|He zAGH4*H1O6v88m&pg0aM|^%8$8WM4GcnI7G?DXpOEy+HDybofFQlq(?{h``P27re*+ z|A#DDdTrc#sU*hmKXhwW=WdWDk8WF~RtAO_wpsA7HvIPD#xam4Ca`;0gStTHBY_TP zYJR{BJ?F}!+tj#~fguRIyY0Whi-(&)r!;`}|F$qNyx2M$beJ3X6eZAFd2l{074m4V z)le_xhLp`O^^g7k-&||K$lt;Wx&dW1Xu%VBeG7lf7qGpq8XnC*G)gi(nt!;IhC zA%X4DI~jB`f=BOW(9sAlq(Cx|^NyjHmv~r$SDKZwzAyrvS=G&2405TZs|I9?>|4;G z3(cSlYYu~!0UZFX0Ro*U+xZo|Clpk-f_rBk&2J(=xnJQ0J3nZ1*inzp8K4E1-L4Ga zGfi40KuyKYR?q^rZdVq^&Q{P2yJIK#AeU}e4#&<`(0saMXDeuFK({N8V`nR9_S~_v z6|@kb+f~4^vlTS?_(Fdf=mP1zAj3U-r-D4^*}E0&IooBRk&pMDovomQAzs-20VN&V z+fAUtI7CGO)V2JV1IkXIT0;X=TX2+UfTBU+#gTq+HUV9l3E9}+07`_Ool|Fk4vzvI zSL4ymTG<3zqYpZE6jX)unof)X`Lq?J#J6`JXfFb&Tm_fg2GC(b$IiW=xl6~+y`T$) z96R@djP>ZYb!%c^cu|-RDwI-G3_yqRXn6Fpa)O%Vod%s5x0(9;Z z$P3`>sX%8Zfp4&F{Z_&YDyDfeAPz6|>^1{!&F2UCN8rV`bck@2XLlI~NSxQB^Zknl zP*E4hZZ{dn&Q`F~y4@5UJ6pld>vofH46eS$8J9rOAih3g^=LF=A~yR z*vp>H$25E`zw@`|{09w^J}CiT$FVmF6zHJZ3$)b{~f!*MtXLG0~5Slzm&b( zDWuz_DF!O&*a=qR*xd?RwCmWt7n}%+9J;+cT)V+(hVcO7amJ61j6WPLPtyUc5;GC&Cw}Cqd{n#~}2zUI^{62$X!cf)dSskZEu~PX+n8mxrO-&4TfiBXch( z*=t^y`Tak$n*r2i-EKCTCuV;C@6m0`3h_r01GAe6#OWY^zVJ&2?fe2IjJg&7|2sB= zQy)W#C^R{GblaY(XJB~Yp7j6!Yg-S{Dnf=53CH8Dpy_TKE}i;6XX`hqJ!4MI=Y&bfdWKQh3n1{}en?hT;D+Pxs}L4D!c4GtXFZg5!v zxrXX%9ca8V1r)a6q5)FLtT^!hf0Rcz>;5_hhDc9Ku!~Fi5Swp&)_^sDBEzS1J*eV+ zF?}~k2y*;>x9vQTv6`u%HIgsfkAjziL(d5S4^o302aW;YyQQ{()|#{)C~<)9!~>oG z*37^V2s#|7+w^Q5XooQ%*S$-QppkWNsc+*_RjUaI2L`3=;@Rq1wV zjsY=1XY9$m_?HS=_Ur<_F;B&#yEvsgIH$W>1C%XgUYr9TP2r*<7bc*$h_En=JrdY54BN&y@fFyS*bkx+jAc)cbU| zoCY;+S`U=6dRks6(FWPqdDElw-3vz0z79wS;3-HC(s4PR%^CYaZ; zyF^8!yF%lI>ev7OeLGVbJi1qdUC{crln1)e^W_0>C#w~d%{+R2R5W&h`Zhkjdsl#h zv-6x!=YJ2#oSch_hDUcRc#TV^3#iTj=SNU2X7S>B3MeJ{fP-ln^iqfJYS5u%FFyVK z|DRs~tl34yf?u!|VsI;{x_Vg-YD6|4@qolLI7x%Xn|?uRdC;m((C{PZ?w)RU7Enh_ zqVqV&IiMpCIbPVj24zzrkIuv24hfX-gBq0TZ$Kl!FL$#tFnl}2QXr)&5ht`OXp*@a-)cprw`uAgy2D zfRa9c2R|!le_oD?h9hYGts1C<5b=U-D#+_ODiNTrxquI7YWe+(JFxq_U)wu2|7GQG zuVDc-0z}_SgT?}1cqFqlc`#o1{{UjdVbB(i?i`f}@a@{)KsDv0tN;H)j!S_=chHCb z|6itq4XIIycrg*Cqk9T?3xG%W78Otf9HdqH4s=@i^m~|4?bn}W&G=F z`M1Q_3NI{@Kmk2n`!o~7nX`)ZJi%JH_ z)&#=?uho4zKYH|v7K82b=)7-u;DzCb|Nme6Id+>gIChIPSzasU@%Vqh@m0i=~v+q2*iYljdJc{H@*03=AONwU>>|;Bd7-GVdXHS14$JpJl@T|1YCJ zDnNJ4`}F3hMC?idb?rOfy-c)Zw=0B-$&Z&v4ln0!x= zf#HQf{Qv(iPlEP7biViLF5q}k9|LK+_<45csDL+cTlrXm%l=YO8Teu)s1ol^QHk*A zdgP;`~;1<$aNFdJ;1Uc%y;Q??z_r=L~|Np=A z0a=b28sd?VF}2nMrJ&G=C;^AY5iZd9K^!qo zJ49$NsCIeL01=me3EF`SKG*KG@LtfMz-ykppj`8sbuXw(?$OIST>#=g53s+$8*E%u zJU|C)xTplYH~|VH@HH^qrZUAKmA1J8p!>06|Nnm-v{B=Wts@@2qTvwX1>hsj zYg7zg1VsJ+|8ngw@TrTSvvnOH+C3oZm=Ym_zJVUSqLCn>ZxA;my!rqCWff>d_$Om2 ztKmju?kRowlh57rNt)DuIbL-9 zx7S4lG-N0NN@1u&hZ7>e12i7prq>D>7)n5^eLy`rju-5apiEez!qHH}{NJinct1G# zE{p)T3$^0^|9{Q3AG9puMPJ1K|KR@ci!EXQ{|CN^i-nX#JRZHaWnv5rFF-RuQP6E) z;Kmem)IIRU;t0@4Wd);W=dl;!;s5`8GTsB<@z;5OpFU{*LO2@KFs)_q>^$PZ_#V{2 zF!k+x?$LSAQ}e!0=Pw_|zb_18|NnPvuu;)3W%c}jn7;NIq>aB#ACtq&S<(tPdFTg~{wE(}!c-}2~u z=h?}k0;=3=Vn8DAkFiKFyhsQKB`DY?f6z$QQ_$puNAFh9`7@w4EvPvHYG}RK1#ULY z1&M=FavCVWI*<8uF9nHs_qv!cdRQLjZ@vmj1ihxrqTr&~qjRao|NsAAbi{!C2d;cr zJ^mkleZsTzzE9^fU(M&9jK@8D-35F)e|a=N_~4;=#G~{1i<{Bl+Z%0sdTq0qL50V^ z7tcV|SMOE_PzZEBf3Z9kRA5#{zjzl3nto*}l?9KEfHd30fu$TgIv;^%hC~<`UUNa% zy&|ASzHK(>4#Wd5OkzR(!~ZYt#DM7MFG9k=q36*JcJgaUAI8^E_Dj&fKd2ls3TC{VZXU-*LpyIa_!)0N?vi@<;A-JuK~&2Kn7 znvZaR&cbCF1*0KA!w_I(U}8uuD#|a?QAkeA%gZlO$jMJkQAo?rD^W-*%Fk8EOUzBR zQczbdR_8)fSdds$oC;A1QlwCluTYR!Qj%Jf2UDMy50Ne|NKMX6%S=ttLo*^VCnrBS zu_RR?IX@@A2y6>OQf5wOUTR_ygPxuqLrF%eLRw}~aS6nr%sd5o}{ zQYzTNsVNZGCPLH|mlS2@rRy;yXMo&T!cd%?n3I^4lZs|NnpdH&*8^1@ups~cAJpVw zV5o-*{0A+41zlVAA0hyghwuoKpbjTk6{xxcGeCn73=9Vv9(?%Gz;NKfhm`VMD}}_o z{F02+B8BqIyp;TMh1|rv#Prl6h0J1w#GInk#FR>fqSCy)%)E34u=2FTlEfSZa6DQm z6r~oHrWThdBFgZUrH!%+yfXO+Tsd*(_3?W6C zNNTJURExP7{PGoGYO_--71Huc^HLNt^AsR@6mnCGixbmRxfl|Q(u+ZgL9_y1mC1>D z>Lm)9dBr7(IXMc!p6<>bJ}wFwiFqkGsYO;Gzk#roLTW{7a#;a5eJG@Xoo~ee_Oq2j za(-z}iUKI%=7Lj`hNgmQsX|g^Nop|{M005#DC$A}%}**x%*<0Lt}HG|%~dEVO03l5 zVt{A^Db+R8Gi6B1EQ!xeEY4;qNlY(hNG>fZO3f=_$Vkmh&nRKY&q-k@&a6s}&&bRx zDP~AYEKX%8E=et5$W5$Z$j!`SNCYMIlK7&;lFWRDr2L|k)S~$E%#@N0hT@XMlGI{` z%;Na8%!=%{nZ@y?Md=`W@>0uEi{jJrlS_+1mc@g0fz_7gSW7G$QTFytl{mw;3!CYR(FG31rzf*f9w3TA@z6sP8-CYOK|=I3RW!C{BQYhVD7Cm4 ztP3g(G5{u=3-u*9^e_#AN#~X3CV|2zwLBh82&^$RCB7uHBqtTs07!U%+?tYK4stoj z6QEKL>=-ZurW+h(sJe?%i%WA#AcYS@QEFmwejZXTOfE_V6@kV1$=RtT$k{U~GcQE} zQ#dEHxFj`CAuYd1Avr%UFEzO&Ge56bAwLg`+Qbxy2?}YMIjIUMsl~}fnFS^JMWEb} zT9BWUgXE6Hf_r|6g1V}nRhh>6V6*y`PXPC;83?${Mka8X3F28&Qb#NJ@j=xGt z1ZM|O^`j195>Q{7S6rF|$`(nf@H7O`r~oRSOJwisMNc~SxFPlck?lG38QR8V|Ff)=co ziy;JLYHnt6ZemGt215|cc-H`bA0JOYsAi4tZg48^Pl+5CSoWx27Xlt$*TIIr9UV03nA#N7& z!68AOe(nt6zVQJ;{vrO({yxDVj*F|0Ylv%nxTl|se>g}W#L?R|-p$`RG?*dY&ow08 z(K*C346Xny6dW25;2#v?>H<*_92p$q>Kh*tio;)+E)$kWv?#4*Iv z-w&^3yoY~K6huY1Z@izQFVw7X-*``FnBH*T_}~!75U3Q?g`R%y@!`Jl&K|DL-f#oJ z%HrKZeSCtQgIryqc8B{y!yrB+G5~4{s#v^>r=yR*JH#AkACTMQeLRCh7S+X1Y+o^7BeWQK?^o;iw;zdfhreB z>dw!D6=k3@8X^YnaFpg{=jE3}Wpxw^N{SRz3v?7pjg1sQjoDH&69v_rQU-lr#i=E^X^F`t48^G>kk(9bVp=LgVQFRwgL`If0YgYO zgHusvdPYfJYH_iGbADb)QGO0XFt}A$oRMFm5R#vt!{D5smtDyal31Lr5K@#_$q?!2 z!Vp}X5nqsBP+9@!Fjl2MwZmzXJGw>XU<+Ax+O+9;MG+BlXW+9Z}C+BB9S z+ANkK+B}va+9H-A+7g7dV;Q1jZ5ixr>=>ebV;Q0yVi}@iV!?e}1X1(k|NpiZ|NqNA z`TyVl$^ZW?Pyhd)^z1*hH_d>N0u7*AmEFLALD|s7$LD|gQ(aq76LD|y4z}eW1 zLD|B@$jH)=LD|jC%)rtSM4Or#7%(WCT9}!-88Ij)rCFF5nKLLGq#2qSrZOlSSr|DQ zxq{R|)TgDTnWq@Q<@G=V6FK_D8Hq)ypuQnPQc-?(Y92#kX-R%@Nn%lnUU3Gfjg zQciwyHfYoVA_0dRGv zyW8NVodT#k0qP{8Nis?^ThC!-VA#aKz>s10|NjeC28I=$|NjfHF)-}#{Qtj1g@M5$ z;{X30tPBi_k^lc|urV-1NB;kB!^XhS5&8dr1RDcGXw?7z3)mPKzDNE4e}RpG!6xSa ze;IZLhKQK||1H=V7^cSj{~yB6!0Z{r|s#i-AEi>;HeyWZbf>|NkFwF)-ZB`v3n67Xt%x_W%DJ+zbqC+5i8` za5FGSW&i(gz|Fvrn)CmE2sZ;mQ2zh_6Sx@|{^$SyzlNKE0kq!#2sZL&FXZ`=LBFez9 zZr1<*4x$VUE_43>4-sWx*fQt;{~S>U2KBlB|2K#-Foezh|9^@o1H<#V|NpNMWnf^R z_y7M1Q3i&U^Zx&TA#2FZTm;C?VBhJ7uY3cv}E5sQXWS0H^ ze@2{vp>g^D|2z^540o6R|8F6|z!0|L|Nj^X28M?#{{NpL!NB0L^8f!W5)2HYtN;K1 zA;G|qyZZls2}uTqO{@R^H;`mtXa+4Dl4M}mv*!Q*1W5*lNo)WAuaIP5C|URa{{)cu zy8r*TNHQ?kumAu5iX;QWhV}pdGe|Ko{8<10zlanAgW-n%|8=Ao7{WIH|G!0wfdO>I zsfaWKL*@4W{}ZGc7@D{L|6d}_z|gh*|NjnY28ILM|Nmbi&A{+*`~Uwtq!}1M_t~D2 zW?+!s@&Eq=X$FRt9smFH$S^Qm2CX2KVPNps|Ns9K83u-r`~UyHBg4Q@ap3>|KQasq zy`U9TvJ4EDkN*E(BFn%q>Dd4Ob7UD9QjY)se?^voq2R>-|1xq644*((bICC|Nj?|XJGI-`~QE4JOe}U+5i7D%|8L|O7)s9n|Iebpz;Ns0|NkZm3=C^7{r_L1 zz`zi4`Tzed3JeU(FaQ7lM1g^U@5=xGB8m(QAy@zZk5FV_NWJ?1e~uyp!``d^|4&h5 zVBoy=|NkD4__hE4|0psrEWGyrzm5_E!{=-N|EDN1Fs!`(|Njgn1_q{E|Nq}nVqg%x z|Np;*G6Tc9`~Uw}C^IlHJox{AgE9ldi--UJ|50XO`1kz(e-9M~2IUw3|0k$0FnGWC z|9^oB1B1!S|Nr->FfjPO{Qv)o3IhYvtN;IHR2dkGU;Y2@qsqXr;`RUk6{_HZpFspd zF)&sIF)&sLFiP{Vb4*}l7XXRNFfcGw1pWW71XTl~K;j@8RXqcP3IhWJSiQyn{|=x{ zw|oL_d=g&#+~ph%4E9piTE;41V?g>{7#J8PIRF0-A6jN$U;vF?$1pH3d<^>k-wI>_ z0|NtSNmBs>1A}<*|NpumF*iPeW~Ri$d>jl6ATvM*6l(d9x&#^%Xq(ai41=fE_Z&%%Yzz>!bGiBG|aPr`{$z=@AThk=0sbkp$K z;Q#+Y7Y#6Y^9l4Yx$sG}F+1}qG_x>u^GP^@RXQF%2D(y|fq?;ZijsWD|NpL_A#t$1 zP9S@oK=wL;>~#d$>j<*fg)e|vlM$Q6Q2&9}CQF3={|^c~2C)AE!2SdI9dt|(NX{1I zKSz+?%RycThcjqtq-E&;{}CWL7mys2FBcz&E4L#bhY14%18BKcVd($=PwJL5oqrVc-c518~^8fWp8T6b7Dr4NOc+_&S(e_$DxU^UYv#;#cO{w39Qr`TX2;yFfi0GF))B8oL55q)DQAgAIMM8_(p^~D83`Pzy`UX zMz||?5Eq|?BSu01g&Y=P4F(2=FH8&!VG;lT+kou#;gj&@Q}E!^aON{`;j?h(b8zDG zaN-N_#5(o7^IL$`!NqB%%fU=PnSRE*wJXjbQ z+T#BIcLbT^1j=)cAiq0;{9Xz3HPp@$76yjdasU6zgVchYlJLFhs=v{|`D41f(65 z7k{uYFvQ0H{|{P33o0X;nV1Seo&nh{!Uh;dmJ2h6k(+4BjdK{}+NB~995=fr} z8v}!8>i_@YAbA%)1EwH84$zH_25bxr8L9vOzXkaPoLyzNpFkU^-T>(jV3uQon*ga7LH(4gnb7nAc4I0RABPjjiQxE*V2AV{Ks%8c{NQFe z@hLFnaPdjFALbKqJjTc2#_b5oaG+3kJbVmVhfHB-V6e*i{~y#uU;vvP4NfN>3=9k# zKsRh>{r_J9aw9nW-QndlxUNEkzZc&F4(31{nIDw5|FAPKCs9h^K7#NiD{{IKh zhl9h!olk+O6r6=XZHE&a3=EB+O?04i3647tkX`P43mBRD`BpHx@@-)B;@iRK$#;O! zh3^ET6W;~K0FYz_%q0vAptgemCj&!c{{R1=xL^R6W6tn$%!Mz2X**IO4l17dp8yAp!L1DiT zqRSDX%aM-*q-PB$1H-WbXn2FeAcD^!lF!49FTjm2!i_J%jW5HIufUVfA(l_Tg^vSN z&OP8{VBjeH{~wgT8GJ!;6bULC7y=j=82)fFFo4ci@PwwNUZ!9^i5_N0K7}?GcRr0~ zR(HOHNWKg=z5+MC3OBw6MmN3=Mkl@rjGlZ6%=a1jG+e-e0cm(KK-zEtTnr4H#sB|< z*4BfoagW0#v6bf~$12HXP_w{~25i44%dR|38F=D@J?A15_S*^Eogj zbAjuA`^Dn_ z|64)n2i(5$0kyB(;N`C)$i5&x2c|829$tI_aiB(m15-4n<*wY`7!G1!0JVj4xEUBe zmHhwz8srAB9cXPha2f9gZpeX}AWnP{PJ9WTd=9029pHCu+Pay!D3qTEGg@=J*Zv`}+yMfA1S5V$?8*$Half%23i zC_#hrE2w;bmZOsrvsPyv7FXpL}o|DG97N929~Io?td43_$Tx z!OOrfq3ZvCO;8ws>VF0Xh8|u9hNV^i|K9~QD8T8%6O;xKZG3PVbmzOk$dt%;gE5Ei z0izS&3q~it4~$Hnd>0tK_--(!@I7E;s^q)D=)!k_(U0#2V;tWD#!$W+jE;O47@5y8 z^4(x`=6k>x2NiM0l9a&tMS_ok;ae><-8k|I7-ak`Jh_ z=7L%eA=>Wld<~4ut&GsJBZ#kq(UGr#v68QY(Ti^aV=P#_ff3Xmf|M@7P!T3as1hb; zn8+=@4#r@<35@LjIMkWz~vgqO{shqE{FLH9FOs7IGy2Ba5~E;;dG8q!09}sFQCKEz);-s|Gxkz zZGr0;M^L)-;+w$4%*cp{U&a9B5l~3qJ$H{PzFoC}{q z7N3S2p8=>Pz`y{?W1ulc@y`GMXM^$yI3B=tr#mP=*KqMkxPV&)uH4L7j9h#xn0)v) zF!}MVV2bA3z!Z!|*K;%ZA?fhtTfyYTw}B~|Zv~Sh-vXv=E{9_|P-Fg)u0|Gx`ljvpuvTtMP`cC+ zW?&GV1RbLR$2n5~*b9z)9H2btBh0|yJL&&_SCIXgd;(#79N@g2BFw;0H3{0zPU90` zS_)BLBh0{HJo*2BOOX0FK7mlE`YFN;3?Y;M|Gx$jPv;Y0S_x6VMwo#?Xv+Wpk3ix~ z%lQO+`8Yt~dIU5k4%$5i5)T5!a~^UugUj<5paeGc|9{XyN07RisRZVBP?`gIAbBdZ zpXdb&cTZ5+=EA4I6vQXt2n}|~xSoj!1H%f?emjtT(DKVegn{ACRH$E4`2;{C2%zhQ z5=0mn{!ab>Ulo*B!TlAq{vvWcF@dR!Zw8Ye-vV%c_2k>Y(e411 z2~$KF7zF2_j@hjM%}dPr4_gKd?)UEzWnlO<8&O9$Gl6=tkT|*_%D^Bp=l_32XdHpe zeIv@ipg8CMf6xu(AYqUggBSyY$sE-3M?{Q)!FUcvp9WkeFhzm;tKjknlwTdh7#IrX z{QvI{G7#Km@c^}15aSlkpmuXUXm|}VNDJDH0UG}kV_;~W^Z$P-eC!K6=I9A(t9gRP zT|Gc!jvk=48mK5TVdJxK2Dev`2YjLZjuT=G3~Lts|Bo0;1e@i`2W$6%!ynx41J#ao z*o*?@aRG4#2HwU0|F?tu1J2{1er7eFfHNNlD4q?(85l|y|Nk!vIzJGcp27AY+>B^n zf(n!SEZ8i9mYp@?3=Fm_pmiX)>_zk)%Mif~Zd-%qjlx&_|GyvVF6h_>Xw-K-SSfUz z_<}eC!@Cv#|6c;Ba{|@buAsKP3#d=$3~JlE@)P_SZ9j z{RJL}bmfDEAJ|{uej6zK9%J(ss2mB9U|{fB_5Xhp)P9t)_hPV1;c2Hvf`MVts{j9= zK-FT5d4bao({ZqC;UoNzv4tHH3=CeYp=BhZ4FD>yPJnH300ap8-=L zpMo>2g#oJ56r>m!p04};L~x1C^&>JDqviocfvF`&fEe zU3=JEn%SBCnRwVib3&l>l_ABzFmEFwU1BeP!RgDD&w*((ws8San{I^bo!I)Vlg z{rP4vdhktPWb(jPwS&SwN0x!%>hAylXM+3$&NJY+bmdcs<lWoAhQhQ z7#N=H{r_JKUT33)4~kh#%wJfsg$AV0TO-H7uH%NOKRPIfYV_?{Q7&>MN ztt(f^F)-Mu{{IhJuL3EHoq5?@`kDJ!{Cin@*qYgyWB5R&4X7V*MUH`C@!|jfUqZvV znaPXq10%%F4{{6)1xKKBL5_R^;d~t6xn~Y}28O~T|Nn0V=>gY&;QWgSBXIixTt0#7 zzv%%GxCx2A)AM;t_g>_Nl4=8dhnSy14$n~b&GJQTZSZ#&)yAa>X6+F zvKNFw;kOS>9kQ7qH8B1K0_s3&LFU2KgJ@8l1`-Fct-+I_AQl3Htmjp7Lfq{V!++l-q7#JAP?ahKpd7XzaVBvwTE*m1v z08fCr=krVeHvx_>J`iWnFe()K_YF!RvWL5m%@ zI+%Ir{s1ptW?*2*fx7SP86R-UL06Xpu?|@sc-a)XI#_(5r*Cw1oX~hfPiN@gr-gast(;B z=;{=p>d@T>8b1UX33DHa4Z`T=!PKGWb9D1SZFi7I4u0f#zRxdAip0qA5?q;vqY7nZJI>JH#g2ObMYvUdRvb>J}$By|lq)WPPV zVD={9PzNgyVCo#u)WPfnuN^_M*8weDVDST9pT@wz083YpP=>5I07U`1I#_)HQ->bE zF!NyP52g-0c8^q^!qma)SC~4`xCS_9b>O)(B>%$d zSkE^Dj&tx_@EnVE%=vL-#LC9n8Nlbub#1u3+k5{)M%}(ESTj z2lFpX9lC!_L7qj5Ur;|EB!uo?m^zq$Vd~KR3sVR4FH9Y}e_`ri{)MSS_b*Hx%)hX7 zfbL(II+%Z9>d^fQTWR|qbsYCZKOdZU> zpy4i1e4zUmrVi#`m^yU-nu9bW#V<@A-M=t(F#p2Tq5Bu64(4B&I&}ZS)WQ41jkE^Dj&t zx_@EnVE%=vL-#LC9n8O=WjY}Dq5Bu64(4B&I&}Yn=ctk57j(=i$UJoa!qmb13sZ;g zUzj?We_`s-{R>kE^Dj&tx_@EnVE%>4qx%=84(4B&I&}BJ+G{7eLB2#_boas9*)Vmm z`VFQ7mR^oS`ya6OPy@7`gRYM0KL@yof~k9eLme}kx&t`W9YwQu0aTr;0>q`7P+AvC z8$xMQC~XO)ZK1Ryly-&Eo=_U*4;bwS6<3)I5eb6wAv^|#FetxlE<_{>%D27=;m1Mw zhEP646gQc=3{CDdDt{*EDhp7597@Avr>uvuQ0avSQ2DrcQ%^!K-%t9p+f1>hn@xl*6Wc<+l4~bm{24?8S9~hq>jjw>lH%8;z zqw#&v_%UewY$zXe9M2CTa0ak9`fXYLi#c&kL0BvalnFpFL0MSrw zASM_?wK2TNhqw>cjynO)oshW%kV05_30_ysz`(G>9wf@Z024O=?~+6Kt3wtd4=a~o z_W%A5@eeEr{z3T{pbI$uL-`QbFfe?8^24AN{7)z!JdXgeVo;C?xiA-_I>!u}F5IAW z7?e(f(q&M(4N6ag(#xRqHYj}zN?(K0&!F@-D9x4#u~!UAt3hcqDD4KN!=Q8;lrDqP zZBTj|lwJmt#L1{Kt#L1{MVfPom4R)f-JP}&Vjhe7EyC|w4n+o1F`D7_3yZ-dgup!78; z{R~QfgVJo!fkrVXtp=sdptKv54ujHZP`V6Cw?XM?Pls*QfuR-Z&Q2HB`W`hpYh(T#J zC~XF%-Jo78*%b@f&D18h{UxU)mp!7E=%?2F+5`)reP}&SiyFuwN zD4hnS%b;`{l%58qmqF=mQ2H2@z6PbALFsQ$nhiSOD+Z<2ptKp3c7xJkP&y4tmqF<^ zC_N2IFN4zCp!6{)eGN)KgVNuiG#hk4TntL9L1{B6?FOa8pmZ9PE`!o-Pq`Wlpe2Bp72X*TEpzZjHOgVJVD+6_vFLFqIoT?VDwp!75-y$nikgVM*K^ff5` z3`&24(rnO)12HJA2BpoQv>TKTgVK=F5<#vgg_Pf*b}Xo`3#u1Ebs;hasX)gd^&kuq z2hj)WB3kVDcBB z@@!E3=;OO*pz^#>dDyr!O#ca}yckp-HXj3%KLV9kgv!I_C1COr^$?aCR30|}0ug0k zH~`ge2$hG;L%`(sK;>*P+C9{65f(f zJ_nR93*~=+(uz=;0V)r(=LJ+8X3hgN{tYz#1vLH%H2wiJ{th($1~mQ(G(OB-FdF6` znEPP7< zAn|=ranRabkoy-v-FXQr4nBhbw2vI@FKLDVH1}+Qs+a8p>1JR6pGUyJzyP`v0ObA} z(4C6lIEBc|vVp=!nxO(s9JC)3q`m}A95hSW|HVgd5Di2chB& z(D6G^dIhOC0~Lpj>w)+FgU;k(gqYs|@(2S1c>g&_{avVfxp^QSWPdV<1zM{L3I`3e z^a+YjP&kA^)q~Fi0L44hoU2fA4@hyqz{mu$SDK*#8lJHH3p(!xWPSnEeAqY)KNBQ= zWkDH-fq?;(jzR8`hKfIiibI?C460CZpM@Y*kaO`stW0RRlmIm!7LUeI_5YyiVc}*2 zb*BT=ouG0MWR5dbeF^w5BgmdRkT~cDDNs1ffr^9o@Pqc9Ld`hK-f z4^^_Y4dS)4=9PF+6~lw=j3khpK$8bu2TW;;?=g zY)|!Ks5or>9<*9wSPK=O4_)^K%ct9*;;?m8&}_+Y04fgaXTj#x&Oyat^9-=hOKc9|(bTcqOt4Rh)sCdg! zh&ZU-24X2f#e1RRpmrNbTmve;04fe@$AH9tK+`QNw15PkoyNexU zaSB*{3M&3U=@2$P0h@Q+3{?-CHwN$V1%Ih`;tf!7*nAN;H%Pq{!v<*i4=W#(py~^t z>S6Jy4;9aVio?`9fW@U4-k_P|2^N=TsDP@6nG?zi$uB%#L9xxi0BZMxSS3*Lc&Iom zT}_6Hul@m2#lQgGr^LX(FduBbAcH2f-vyfoS_V}Qo7V&HQDR_V@P>xlE>HuNfq?;!+F@We|rS zfKUv7!Qz4ps?hL(sb>NABLo>FpyIIkLVl>Y15{iL>K+BCxB*lgrd|guF3n&7bq_3l z&B5YQ46yM zTnQ!)bw6nD7APrdLB(O~IbiC|pyIIg8!&MPs5orB229)wDh^wp0SnJiu(%)tZ2U_I zWGMp!Lp)R*Ha-MP=b2D(*!U4lycjAD8~=fc*FeQ#<4Q2`HmEpkoD3%34Hbutm%+p* zK*eF>W-#%oP;uD!8BBZ@R2(*r1{0qT6^D(d!Niw9#bM)WF!7a8aoG48OneUV<0`59pA~3fuA7~8js+8`V0&V%sdeHNJ7II z)Sm&FBLfv*S_k4WFo4gaW?*2@28%<)kV$78;{G_q6L~=S1=$1$y8x`7pTPh+;0HdZ znt_3#8!Qe{gG_?%Fvk|Z>v5<*fH*$O{TLWS2nLav*Ug z0S4ImH;5<$g9=C-#zCUZ!Q%W3?>ixau=xugusB2vnT*CEo`*xc9*6h@9O8>{h;PIp zei(=NH5}qEafpAzAa-qVV8r& z`5CyO`3QEFOB+lkT>c=N0AY*qGcX7;2*L;6VD-By4)vBe#659{hvN{>#35dVL%b7*_+%X7 zi^1aj3=^UC0(3frVLezJ;&){75V#&k7J;zu@Pp27!K~k3;xOkI4smV)?CDtwhqwg} zaW}9yKZ7hZoxsMgqM_nK(19XYJ1`k64)F^zSp*j6XIKw9q@IC+0XDwZ3KmCJ31QC= zU|@jNSr8EjxfzH0Lpa1Q;t;=&L;MX6@n1N^g#@vOvl>{OpFsdxuYorkGcYjdL&epg z^$x6_wS|gngAdGNV1TW=bpeY*{EkdU;1Ey1A)W^o=V$1HPISP=2|?#Yg6iu9Zy~0G z&zxdlVCV#^hlnAQvvG*8!XbVLEY8nR3Qa%Y^MpWGm4L;OO@^>v;}HJ=7UyT+ftmw5 z(@01Nd-y8}F)#=+Nij4)+y4;L85s1y>iHQKK+`Swo&*L426M1DOcjFm#UT#5PY7Fk zpct&4pMe27kp-RpVQ7GgYrKM(4L;9|fq`KGSR5jTOm4;@ehe(m&!7Mu2#4jnhfx2* z*4M$zdIwd{3(a@1d4!)}ahNIu%?&ye62XPDByfmpg2nk6bfF%C`PT?64%ZH6IN=Zv z#vvYuLp%?McpVP$F0eR1!!@WoVf~9qP;m`teF}{>h9zKexU=Dm?Ks3w;t;ChL!3<% z6#o1StlW_HIrv;B(7Y;G9PS-BLmn*74?n*Gb~dvnSRAel&M?Cv9s?HVXV`olq7*vK zz>o$NPk93ohpj(p1B*k%kV()!7En2F1TE)b^CpXNsNal3{1{lApWzF1ff8&V&2^~w z8!kxrz}7+Chl=|^%UkeWHVh05AHd?sE`+cd#X#W-Ryf4H!Q%W3 z70~<$-44SL4;DwZ5yCFPA>M*Rd@2s{J=4?BDH6IdML zW@M689D8_5ibL|<3g|jFm>xN>dVU6-j}Q^?d5jDU40>R3h!`?yg+tsAEY8o+4ILMP zPH!{Bg2j~(| zKf@|$`wDypENHwAEDmu4GUx+pzw!?A(Nsw#Eo!>yWkKH!6BXw7UyTU3@r~~>*xx=;>a$6 zu&cr1{0z*{dJ0-hFw}#^A!?Axi8#cUfW`S4VEc<< z4Tty>usA=%N@#-=v`!bK<1Tt~gImEh(-nE-6Z^EYD0S$zaG!ttctU%t=jw++ALj3cm1| zp{O)3FEcNl0dz-kNj~VB^xXWsVutd}ypnwAjp$%D)U3?p{5*z=)S{yNqLk9y+)4(h zI7Dk{K}uptD(IH+jLf`}VuqB0N`}OOf}Bc(P;og!QU(K5A;f(kGt*P^a~YEJ^U^ZY zON&w&iok|I?FYGtD6>2%e@tFn5@u2Ix;|mgtic?eKlk;@>>ab|jQMotPta!zVuUTFcyO3~_jots#k&5&M{n3SDbS za%nL`acW))I7;-A^Ye#f^OEy&6N^hyi=awChCvMlxw|AMu`<831f&7NDo#vGWk{_k$S+QXIkF_N1nkEW zh-W}Igs19d78fPz88ASEU}-QN5?i2{gDEJ^OfN|-%4I0dOwTASfErnznU|7ZUQ$_* zib(s=xGT-eP0RzOXAq+RDwvX5TvC)@3FW2cm82Fy*}3^8nfZBePM)3t10=}2 zc_kU3#0m)_P{{;gm1U-uGk_I9iX|`;q_8SKKNlROAWlhQI!u3JI!FK#Ke_p(#idR1?8KCmA_fp6KD8h}Cnp}9GQjy0S_%}SaN=`P^B5p1(^4QZ zIhm=TLJE}AAz8YhD77H5C^a6G&`@$b*z9<$(vaL%l39|I3cF1n6fvnqC9r@jDh1{4 z+{6M{YAa9FGh|512icvRpOary3{G*WISl2w$r-81*`UHIrJyn%;!e14Qp$5vGK(0% zSJ*>tu!mn$55A?oI0JEyeQ^duPGWIMacT}IFylcbQbB%cUP^pUW=U#MVve2#0~SF` zY=VXcSVax7iW*@RHO4Axf>qQMtEd@PQFE-KSiNS5)o(^v{bppy0Lo0sxhV`Oxv6=j zAO@Du!m1Z*uwV@ptbu|xOt1zC)(|mg$Scdt1-Z94xhNkL{UCN`UUGa+W_m^mLrN-) zRZ*0fmr_(-lvn`qBPcmCB5RK%xc=4Hl%YLm+N)Vz|SN`|ze#N5<) zNb@8<9*GC3vyqji6qV~GGnAI3S)`;U=clA5RhFcJxZrxPv?LA8uK)!OxShd}nv;`R zP@GvDUk++h=^296f-0JlwA{oBhP?dT#FFHU)M7mYhSHME9KB?Q_;|MaOnnVXZD6c1@x#3vW!7ZjwX#1|(g=A@>?XXb*6bWnMglUZC6pPpJ052-mA z+>)I$5{rUUOB_>DKrDucAW&sm6$e8^W?pJhVs2t!X=+hraB@*2l5!mbxmwK&5& zwK6F`u_(nC(spBra4$+s3QjF5OD$rEa8E4>_w@tCe2xdW!5$wEs(s_Zt?T%Bu$cv! z$t9&lsZev{Ar*59Luy58a#;b2S5hl7OHc%$Ar6V*0EjsZ5y7b?&Y)m$$uH0I%}+^X zhzLr|EQZB3$bZlT2uZsr2@n$Kpf)TdvB!g2`zfi(iOCtM4B(Og{X) z8c-evMN@ie2{`XEM1^M-B(FN>=N9A_XO^VGobH`k2@TD};$l$zF+ejDG$p|^JtP7cB65>LQY%Ve z{tL+m@q99iOTh6ADNHG%)E54p$rj_3Ix&i0VVz}##^h_Bd9CLE=%fVq)%#c`;pNS$1 zvH~=s#Sq~P>QVV77H5N%B0LQ$&yq^h7(nSA8in9O2~=>TlsM1(Zht!X9BppG8M$;p0+xv31fskxxuEQ*^!*%DH! z#^pSX%_~E3NJMaIPHJ+A zXI?>R3AiKzv)sUAFra``1#l@g}Q&hqb;hC3N z;t6h@!W)f_DJf3*`8f;`!IgQ*U~3@OX6EPlm*=GxL81y=N2G(w21Fd@fg3EDRjCDu zB^e-{c`1;{1qCT&lp!KKv8Vu6e?l4!kU|j@?cj1YEx#xpRG)*&$o#yVN`|z|r2M?Z z`4&{6fn&P3q$oKTwVebCLs%dNX-dutU_8rkOmC6tpID}dlow;mt>ZuB6NYu5pZpr zo0^O6=Lnb7w9LFzXd@otU$_H7c?6XD!R{$a1w|vwNfBu!plZ*xq69Qn18zZqDh61q z4A!cRPtAi2>VTpYuGbUPvdeP>4SN=s6y+tOg&a8cKouc)c!wbZ+--p+8l-rH_Htl# zY`+ zI2GD1%PE4ky%~y2Qc{ahi)XiFNdF55Uy;k5iB)TS$_-3uB%$;<(g_YEk@FUe2N&nad|%FIJ; ze1oG8mQAtrl!{WpIRYh>6{nVf3acEnHcAk9+%K;LS`mN-0rN^ggKc0tK;yv-1>kIs zVpx1Cs3d{4n4w_?s!zcEnG{%01kq*z2Si#vXq+~+Bt5a97}nr`CJ9jNg9=wjvjLn% zVYNQWkR-G*3T@WM$LC}wB|}Qu_}s)yM9dqTFo2^E#nqq!4K848%m9kh0@OMK=4)t{ z2m$rSQ!-N-(vtI#+7O^z3yLJPV!OB?vnVkK?08t@fUHbK%L&1tt|urpAhlt@g(1vx zkcXg6F0kjpnG{qcf{N7u(0mU=L_lg@3TW~QUIr$CYZULyoE&HH=q>}e(FgY~$jP7( z0yPUjV-S$|M#_K4wG*g=0BV&o=oMGymLw)I=oOa~LFfz^3zUG13K;bA@=H?n((_97 zU}L{XLP@2WIVrlCDNrFtCr@3_@Hkj`Mq+UWgI-Ey9;iqL(8=a(?(fr=*vJ*{$lw`72aFBU1+q)8C?D*;#N5ne29WU}YZ&xE(hPb@#l;MIpz&!?gB5A|l58gE zoMO;SG3fk5kRcM#1DIemY<(3-4unB!K{O15&g}usJ;U@{K=s3D*nUu$dJY%^N`uxT zBI^g8zX6hh(V)9PL3|j7t=EU~JsBWp6T|F>o!14U(d|dq4?1V*+yDRhF#WLemti#Q zJbCDL82EY4F#AEr;)2dzhUtf$&kUn?Ko<&w{0Is=nExf927uNJfPxIBAGA&mBn6{k z=R1SMKp12Oh=yU%IuOuV$}s(~^OIpTblW@p{Aidu7!6uR^bg7Z7oZE$U^Hy{9Nqme z`+K2hXTi*aoevG8Vdq1mhab%UpmU-?aRNJ!3U=N!jE0?04bl%<(}x~@pks$XVFHo| zy9wrCC=ENG2P_A5D~t`ILB~KL>sNs4gV7G4$OS2cV%R=r7!5kU2U&jtbR7bWhPfB2 z9iDz*;v2x{mLbmfhOJ|O(aNv|(@+Co_CmSfa~hECp8{PE1*17(3ZXP8tf5RW1v+j9 zq#qW4@B?X}2h+gPFIX|sIe0KW=r|T+{jhzfFnSJbBQ8_}y8WQ_U+)ImH31{lqP zmVsg61~rc14(JSg(EdKC0Bj#|fG5NpboDSkj0SB>0__C^ z1~Et&ju$}fw}4)pCda_Q09q;!^Ea&g0yz)ncDQB+28J7;1M3(V7)n7EERqDwUYHQ5 VdcdaNcPm6CG&{hQ!}r<41OR)1RKoxO literal 0 HcmV?d00001 diff --git a/KleinDwm/source/dwm-msg b/KleinDwm/source/dwm-msg new file mode 100755 index 0000000000000000000000000000000000000000..464f385d255c123a2a84c10fcb39db0ae2819990 GIT binary patch literal 17896 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 literal 0 HcmV?d00001 diff --git a/KleinDwm/source/dwm-msg.c b/KleinDwm/source/dwm-msg.c new file mode 100644 index 0000000..1971d32 --- /dev/null +++ b/KleinDwm/source/dwm-msg.c @@ -0,0 +1,548 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPC_MAGIC "DWM-IPC" +// clang-format off +#define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C' } +// clang-format on +#define IPC_MAGIC_LEN 7 // Not including null char + +#define IPC_EVENT_TAG_CHANGE "tag_change_event" +#define IPC_EVENT_CLIENT_FOCUS_CHANGE "client_focus_change_event" +#define IPC_EVENT_LAYOUT_CHANGE "layout_change_event" +#define IPC_EVENT_MONITOR_FOCUS_CHANGE "monitor_focus_change_event" +#define IPC_EVENT_FOCUSED_TITLE_CHANGE "focused_title_change_event" +#define IPC_EVENT_FOCUSED_STATE_CHANGE "focused_state_change_event" + +#define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str)) +#define YINT(num) yajl_gen_integer(gen, num) +#define YDOUBLE(num) yajl_gen_double(gen, num) +#define YBOOL(v) yajl_gen_bool(gen, v) +#define YNULL() yajl_gen_null(gen) +#define YARR(body) \ + { \ + yajl_gen_array_open(gen); \ + body; \ + yajl_gen_array_close(gen); \ + } +#define YMAP(body) \ + { \ + yajl_gen_map_open(gen); \ + body; \ + yajl_gen_map_close(gen); \ + } + +typedef unsigned long Window; + +const char *DEFAULT_SOCKET_PATH = "/tmp/dwm.sock"; +static int sock_fd = -1; +static unsigned int ignore_reply = 0; + +typedef enum IPCMessageType { + IPC_TYPE_RUN_COMMAND = 0, + IPC_TYPE_GET_MONITORS = 1, + IPC_TYPE_GET_TAGS = 2, + IPC_TYPE_GET_LAYOUTS = 3, + IPC_TYPE_GET_DWM_CLIENT = 4, + IPC_TYPE_SUBSCRIBE = 5, + IPC_TYPE_EVENT = 6 +} IPCMessageType; + +// Every IPC message must begin with this +typedef struct dwm_ipc_header { + uint8_t magic[IPC_MAGIC_LEN]; + uint32_t size; + uint8_t type; +} __attribute((packed)) dwm_ipc_header_t; + +static int +recv_message(uint8_t *msg_type, uint32_t *reply_size, uint8_t **reply) +{ + uint32_t read_bytes = 0; + const int32_t to_read = sizeof(dwm_ipc_header_t); + char header[to_read]; + char *walk = header; + + // Try to read header + while (read_bytes < to_read) { + ssize_t n = read(sock_fd, header + read_bytes, to_read - read_bytes); + + if (n == 0) { + if (read_bytes == 0) { + fprintf(stderr, "Unexpectedly reached EOF while reading header."); + fprintf(stderr, + "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n", + read_bytes, to_read); + return -2; + } else { + fprintf(stderr, "Unexpectedly reached EOF while reading header."); + fprintf(stderr, + "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n", + read_bytes, to_read); + return -3; + } + } else if (n == -1) { + return -1; + } + + read_bytes += n; + } + + // Check if magic string in header matches + if (memcmp(walk, IPC_MAGIC, IPC_MAGIC_LEN) != 0) { + fprintf(stderr, "Invalid magic string. Got '%.*s', expected '%s'\n", + IPC_MAGIC_LEN, walk, IPC_MAGIC); + return -3; + } + + walk += IPC_MAGIC_LEN; + + // Extract reply size + memcpy(reply_size, walk, sizeof(uint32_t)); + walk += sizeof(uint32_t); + + // Extract message type + memcpy(msg_type, walk, sizeof(uint8_t)); + walk += sizeof(uint8_t); + + (*reply) = malloc(*reply_size); + + // Extract payload + read_bytes = 0; + while (read_bytes < *reply_size) { + ssize_t n = read(sock_fd, *reply + read_bytes, *reply_size - read_bytes); + + if (n == 0) { + fprintf(stderr, "Unexpectedly reached EOF while reading payload."); + fprintf(stderr, "Read %" PRIu32 " bytes, expected %" PRIu32 " bytes.\n", + read_bytes, *reply_size); + free(*reply); + return -2; + } else if (n == -1) { + if (errno == EINTR || errno == EAGAIN) continue; + free(*reply); + return -1; + } + + read_bytes += n; + } + + return 0; +} + +static int +read_socket(IPCMessageType *msg_type, uint32_t *msg_size, char **msg) +{ + int ret = -1; + + while (ret != 0) { + ret = recv_message((uint8_t *)msg_type, msg_size, (uint8_t **)msg); + + if (ret < 0) { + // Try again (non-fatal error) + if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue; + + fprintf(stderr, "Error receiving response from socket. "); + fprintf(stderr, "The connection might have been lost.\n"); + exit(2); + } + } + + return 0; +} + +static ssize_t +write_socket(const void *buf, size_t count) +{ + size_t written = 0; + + while (written < count) { + const ssize_t n = + write(sock_fd, ((uint8_t *)buf) + written, count - written); + + if (n == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + continue; + else + return n; + } + written += n; + } + return written; +} + +static void +connect_to_socket() +{ + struct sockaddr_un addr; + + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + + // Initialize struct to 0 + memset(&addr, 0, sizeof(struct sockaddr_un)); + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, DEFAULT_SOCKET_PATH); + + connect(sock, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)); + + sock_fd = sock; +} + +static int +send_message(IPCMessageType msg_type, uint32_t msg_size, uint8_t *msg) +{ + dwm_ipc_header_t header = { + .magic = IPC_MAGIC_ARR, .size = msg_size, .type = msg_type}; + + size_t header_size = sizeof(dwm_ipc_header_t); + size_t total_size = header_size + msg_size; + + uint8_t buffer[total_size]; + + // Copy header to buffer + memcpy(buffer, &header, header_size); + // Copy message to buffer + memcpy(buffer + header_size, msg, header.size); + + write_socket(buffer, total_size); + + return 0; +} + +static int +is_float(const char *s) +{ + size_t len = strlen(s); + int is_dot_used = 0; + int is_minus_used = 0; + + // Floats can only have one decimal point in between or digits + // Optionally, floats can also be below zero (negative) + for (int i = 0; i < len; i++) { + if (isdigit(s[i])) + continue; + else if (!is_dot_used && s[i] == '.' && i != 0 && i != len - 1) { + is_dot_used = 1; + continue; + } else if (!is_minus_used && s[i] == '-' && i == 0) { + is_minus_used = 1; + continue; + } else + return 0; + } + + return 1; +} + +static int +is_unsigned_int(const char *s) +{ + size_t len = strlen(s); + + // Unsigned int can only have digits + for (int i = 0; i < len; i++) { + if (isdigit(s[i])) + continue; + else + return 0; + } + + return 1; +} + +static int +is_signed_int(const char *s) +{ + size_t len = strlen(s); + + // Signed int can only have digits and a negative sign at the start + for (int i = 0; i < len; i++) { + if (isdigit(s[i])) + continue; + else if (i == 0 && s[i] == '-') { + continue; + } else + return 0; + } + + return 1; +} + +static void +flush_socket_reply() +{ + IPCMessageType reply_type; + uint32_t reply_size; + char *reply; + + read_socket(&reply_type, &reply_size, &reply); + + free(reply); +} + +static void +print_socket_reply() +{ + IPCMessageType reply_type; + uint32_t reply_size; + char *reply; + + read_socket(&reply_type, &reply_size, &reply); + + printf("%.*s\n", reply_size, reply); + fflush(stdout); + free(reply); +} + +static int +run_command(const char *name, char *args[], int argc) +{ + const unsigned char *msg; + size_t msg_size; + + yajl_gen gen = yajl_gen_alloc(NULL); + + // Message format: + // { + // "command": "", + // "args": [ ... ] + // } + // clang-format off + YMAP( + YSTR("command"); YSTR(name); + YSTR("args"); YARR( + for (int i = 0; i < argc; i++) { + if (is_signed_int(args[i])) { + long long num = atoll(args[i]); + YINT(num); + } else if (is_float(args[i])) { + float num = atof(args[i]); + YDOUBLE(num); + } else { + YSTR(args[i]); + } + } + ) + ) + // clang-format on + + yajl_gen_get_buf(gen, &msg, &msg_size); + + send_message(IPC_TYPE_RUN_COMMAND, msg_size, (uint8_t *)msg); + + if (!ignore_reply) + print_socket_reply(); + else + flush_socket_reply(); + + yajl_gen_free(gen); + + return 0; +} + +static int +get_monitors() +{ + send_message(IPC_TYPE_GET_MONITORS, 1, (uint8_t *)""); + print_socket_reply(); + return 0; +} + +static int +get_tags() +{ + send_message(IPC_TYPE_GET_TAGS, 1, (uint8_t *)""); + print_socket_reply(); + + return 0; +} + +static int +get_layouts() +{ + send_message(IPC_TYPE_GET_LAYOUTS, 1, (uint8_t *)""); + print_socket_reply(); + + return 0; +} + +static int +get_dwm_client(Window win) +{ + const unsigned char *msg; + size_t msg_size; + + yajl_gen gen = yajl_gen_alloc(NULL); + + // Message format: + // { + // "client_window_id": "" + // } + // clang-format off + YMAP( + YSTR("client_window_id"); YINT(win); + ) + // clang-format on + + yajl_gen_get_buf(gen, &msg, &msg_size); + + send_message(IPC_TYPE_GET_DWM_CLIENT, msg_size, (uint8_t *)msg); + + print_socket_reply(); + + yajl_gen_free(gen); + + return 0; +} + +static int +subscribe(const char *event) +{ + const unsigned char *msg; + size_t msg_size; + + yajl_gen gen = yajl_gen_alloc(NULL); + + // Message format: + // { + // "event": "", + // "action": "subscribe" + // } + // clang-format off + YMAP( + YSTR("event"); YSTR(event); + YSTR("action"); YSTR("subscribe"); + ) + // clang-format on + + yajl_gen_get_buf(gen, &msg, &msg_size); + + send_message(IPC_TYPE_SUBSCRIBE, msg_size, (uint8_t *)msg); + + if (!ignore_reply) + print_socket_reply(); + else + flush_socket_reply(); + + yajl_gen_free(gen); + + return 0; +} + +static void +usage_error(const char *prog_name, const char *format, ...) +{ + va_list args; + va_start(args, format); + + fprintf(stderr, "Error: "); + vfprintf(stderr, format, args); + fprintf(stderr, "\nusage: %s [...]\n", prog_name); + fprintf(stderr, "Try '%s help'\n", prog_name); + + va_end(args); + exit(1); +} + +static void +print_usage(const char *name) +{ + printf("usage: %s [options] [...]\n", name); + puts(""); + puts("Commands:"); + puts(" run_command [args...] Run an IPC command"); + puts(""); + puts(" get_monitors Get monitor properties"); + puts(""); + puts(" get_tags Get list of tags"); + puts(""); + puts(" get_layouts Get list of layouts"); + puts(""); + puts(" get_dwm_client Get dwm client proprties"); + puts(""); + puts(" subscribe [events...] Subscribe to specified events"); + puts(" Options: " IPC_EVENT_TAG_CHANGE ","); + puts(" " IPC_EVENT_LAYOUT_CHANGE ","); + puts(" " IPC_EVENT_CLIENT_FOCUS_CHANGE ","); + puts(" " IPC_EVENT_MONITOR_FOCUS_CHANGE ","); + puts(" " IPC_EVENT_FOCUSED_TITLE_CHANGE ","); + puts(" " IPC_EVENT_FOCUSED_STATE_CHANGE); + puts(""); + puts(" help Display this message"); + puts(""); + puts("Options:"); + puts(" --ignore-reply Don't print reply messages from"); + puts(" run_command and subscribe."); + puts(""); +} + +int +main(int argc, char *argv[]) +{ + const char *prog_name = argv[0]; + + connect_to_socket(); + if (sock_fd == -1) { + fprintf(stderr, "Failed to connect to socket\n"); + return 1; + } + + int i = 1; + if (i < argc && strcmp(argv[i], "--ignore-reply") == 0) { + ignore_reply = 1; + i++; + } + + if (i >= argc) usage_error(prog_name, "Expected an argument, got none"); + + if (strcmp(argv[i], "help") == 0) + print_usage(prog_name); + else if (strcmp(argv[i], "run_command") == 0) { + if (++i >= argc) usage_error(prog_name, "No command specified"); + // Command name + char *command = argv[i]; + // Command arguments are everything after command name + char **command_args = argv + ++i; + // Number of command arguments + int command_argc = argc - i; + run_command(command, command_args, command_argc); + } else if (strcmp(argv[i], "get_monitors") == 0) { + get_monitors(); + } else if (strcmp(argv[i], "get_tags") == 0) { + get_tags(); + } else if (strcmp(argv[i], "get_layouts") == 0) { + get_layouts(); + } else if (strcmp(argv[i], "get_dwm_client") == 0) { + if (++i < argc) { + if (is_unsigned_int(argv[i])) { + Window win = atol(argv[i]); + get_dwm_client(win); + } else + usage_error(prog_name, "Expected unsigned integer argument"); + } else + usage_error(prog_name, "Expected the window id"); + } else if (strcmp(argv[i], "subscribe") == 0) { + if (++i < argc) { + for (int j = i; j < argc; j++) subscribe(argv[j]); + } else + usage_error(prog_name, "Expected event name"); + // Keep listening for events forever + while (1) { + print_socket_reply(); + } + } else + usage_error(prog_name, "Invalid argument '%s'", argv[i]); + + return 0; +} diff --git a/KleinDwm/source/dwm-msg.o b/KleinDwm/source/dwm-msg.o new file mode 100644 index 0000000000000000000000000000000000000000..dff4874ed91030c264b03f52d0095a34a2dc4e29 GIT binary patch literal 14040 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 +#include +#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 + +#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, SchemeUrg, 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 struct TagState TagState; +struct TagState { + int selected; + int occupied; + int urgent; +}; + +typedef struct ClientState ClientState; +struct ClientState { + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; +}; + +typedef union { + long i; + unsigned long 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; + ClientState prevstate; +}; + +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; + +typedef struct Pertag Pertag; +struct Monitor { + char ltsymbol[16]; + char lastltsymbol[16]; + float mfact; + int nmaster; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; + TagState tagstate; + int showbar; + int topbar; + Client *clients; + Client *sel; + Client *lastsel; + Client *stack; + Monitor *next; + Window barwin; + const Layout *lt[2]; + const Layout *lastlt; + Pertag *pertag; +}; + +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 Monitor *numtomon(int num); +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 focusnthmon(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 int handlexevent(struct epoll_event *ev); +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 runautostart(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 setlayoutsafe(const Arg *arg); +static void setmfact(const Arg *arg); +static void setup(void); +static void setupepoll(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 tagnthmon(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 autostartblocksh[] = "autostart_blocking.sh"; +static const char autostartsh[] = "autostart.sh"; +static const char broken[] = "broken"; +static const char dwmdir[] = "dwm"; +static const char localshare[] = ".local/share"; +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 epoll_fd; +static int dpy_fd; +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, *lastselmon; +static Window root, wmcheckwin; +static KeySym keychain = -1; + +#include "ipc.h" +/* configuration, allows nested code to access above variables */ +#include "config.h" + +#ifdef VERSION +#include "IPCClient.c" +#include "yajl_dumps.c" +#include "ipc.c" +#endif + +struct Pertag { + unsigned int curtag, prevtag; /* current and previous tag */ + int nmasters[LENGTH(tags) + 1]; /* number of windows in master area */ + float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */ + unsigned int sellts[LENGTH(tags) + 1]; /* selected layouts */ + const Layout *ltidxs[LENGTH(tags) + 1][2]; /* matrix of tags and layouts indexes */ + int showbars[LENGTH(tags) + 1]; /* display bar for the current tag */ +}; + +/* 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]); + + ipc_cleanup(); + + if (close(epoll_fd) < 0) { + fprintf(stderr, "Failed to close epoll file descriptor\n"); + } +} + +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; + unsigned int i; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; + m->mfact = mfact; + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; + m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + m->pertag = ecalloc(1, sizeof(Pertag)); + m->pertag->curtag = m->pertag->prevtag = 1; + + for (i = 0; i <= LENGTH(tags); i++) { + m->pertag->nmasters[i] = m->nmaster; + m->pertag->mfacts[i] = m->mfact; + + m->pertag->ltidxs[i][0] = m->lt[0]; + m->pertag->ltidxs[i][1] = m->lt[1]; + m->pertag->sellts[i] = m->sellt; + + m->pertag->showbars[i] = m->showbar; + } + + 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; +} + +Monitor * +numtomon(int num) +{ + Monitor *m = NULL; + int i = 0; + + for(m = mons, i=0; m->next && i < num; m = m->next){ + i++; + } + return m; +} + +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 +focusnthmon(const Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + + if ((m = numtomon(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); + } + } +} + +int +handlexevent(struct epoll_event *ev) +{ + if (ev->events & EPOLLIN) { + XEvent ev; + while (running && XPending(dpy)) { + XNextEvent(dpy, &ev); + if (handler[ev.type]) { + handler[ev.type](&ev); /* call handler */ + ipc_send_events(mons, &lastselmon, selmon); + } + } + } else if (ev-> events & EPOLLHUP) { + return -1; + } + + return 0; +} + +void +incnmaster(const Arg *arg) +{ + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag] = 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) +{ + int event_count = 0; + const int MAX_EVENTS = 10; + struct epoll_event events[MAX_EVENTS]; + + XSync(dpy, False); + + /* main event loop */ + while (running) { + event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); + + for (int i = 0; i < event_count; i++) { + int event_fd = events[i].data.fd; + DEBUG("Got event from fd %d\n", event_fd); + + if (event_fd == dpy_fd) { + // -1 means EPOLLHUP + if (handlexevent(events + i) == -1) + return; + } else if (event_fd == ipc_get_sock_fd()) { + ipc_handle_socket_epoll_event(events + i); + } else if (ipc_is_client_registered(event_fd)){ + if (ipc_handle_client_epoll_event(events + i, mons, &lastselmon, selmon, + tags, LENGTH(tags), layouts, LENGTH(layouts)) < 0) { + fprintf(stderr, "Error handling IPC event on fd %d\n", event_fd); + } + } else { + fprintf(stderr, "Got event from unknown fd %d, ptr %p, u32 %d, u64 %lu", + event_fd, events[i].data.ptr, events[i].data.u32, + events[i].data.u64); + fprintf(stderr, " with events %d\n", events[i].events); + return; + } + } + } +} + +void +runautostart(void) +{ + char *pathpfx; + char *path; + char *xdgdatahome; + char *home; + struct stat sb; + + if ((home = getenv("HOME")) == NULL) + /* this is almost impossible */ + return; + + /* if $XDG_DATA_HOME is set and not empty, use $XDG_DATA_HOME/dwm, + * otherwise use ~/.local/share/dwm as autostart script directory + */ + xdgdatahome = getenv("XDG_DATA_HOME"); + if (xdgdatahome != NULL && *xdgdatahome != '\0') { + /* space for path segments, separators and nul */ + pathpfx = ecalloc(1, strlen(xdgdatahome) + strlen(dwmdir) + 2); + + if (sprintf(pathpfx, "%s/%s", xdgdatahome, dwmdir) <= 0) { + free(pathpfx); + return; + } + } else { + /* space for path segments, separators and nul */ + pathpfx = ecalloc(1, strlen(home) + strlen(localshare) + + strlen(dwmdir) + 3); + + if (sprintf(pathpfx, "%s/%s/%s", home, localshare, dwmdir) < 0) { + free(pathpfx); + return; + } + } + + /* check if the autostart script directory exists */ + if (! (stat(pathpfx, &sb) == 0 && S_ISDIR(sb.st_mode))) { + /* the XDG conformant path does not exist or is no directory + * so we try ~/.dwm instead + */ + char *pathpfx_new = realloc(pathpfx, strlen(home) + strlen(dwmdir) + 3); + if(pathpfx_new == NULL) { + free(pathpfx); + return; + } + pathpfx = pathpfx_new; + + if (sprintf(pathpfx, "%s/.%s", home, dwmdir) <= 0) { + free(pathpfx); + return; + } + } + + /* try the blocking script first */ + path = ecalloc(1, strlen(pathpfx) + strlen(autostartblocksh) + 2); + if (sprintf(path, "%s/%s", pathpfx, autostartblocksh) <= 0) { + free(path); + free(pathpfx); + } + + if (access(path, X_OK) == 0) + system(path); + + /* now the non-blocking script */ + if (sprintf(path, "%s/%s", pathpfx, autostartsh) <= 0) { + free(path); + free(pathpfx); + } + + if (access(path, X_OK) == 0) + system(strcat(path, " &")); + + free(pathpfx); + free(path); +} + +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 +setgaps(const Arg *arg) +{ + if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) + selmon->gappx = 0; + else + selmon->gappx += arg->i; + arrange(selmon); +} + +void +setlayout(const Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag] ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); + if (selmon->sel) + arrange(selmon); + else + drawbar(selmon); +} + +void +setlayoutsafe(const Arg *arg) +{ + const Layout *ltptr = (Layout *)arg->v; + if (ltptr == 0) + setlayout(arg); + for (int i = 0; i < LENGTH(layouts); i++) { + if (ltptr == &layouts[i]) + setlayout(arg); + } +} + +/* 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 = selmon->pertag->mfacts[selmon->pertag->curtag] = f; + arrange(selmon); +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + + /* clean up any zombies immediately */ + 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 = drw->fonts->h + user_bh; + 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); + setupepoll(); +} + +void +setupepoll(void) +{ + epoll_fd = epoll_create1(0); + dpy_fd = ConnectionNumber(dpy); + struct epoll_event dpy_event; + + // Initialize struct to 0 + memset(&dpy_event, 0, sizeof(dpy_event)); + + DEBUG("Display socket is fd %d\n", dpy_fd); + + if (epoll_fd == -1) { + fputs("Failed to create epoll file descriptor", stderr); + } + + dpy_event.events = EPOLLIN; + dpy_event.data.fd = dpy_fd; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, dpy_fd, &dpy_event)) { + fputs("Failed to add display file descriptor to epoll", stderr); + close(epoll_fd); + exit(1); + } + + if (ipc_init(ipcsockpath, epoll_fd, ipccommands, LENGTH(ipccommands)) < 0) { + fputs("Failed to initialize IPC\n", stderr); + } +} + +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) +{ + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + 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 +tagnthmon(const Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendmon(selmon->sel, numtomon(arg->i)); +} + +void +tile(Monitor *m) +{ + unsigned int i, n, h, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else + mw = m->ww - m->gappx; + for (i = 0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + if (i < m->nmaster) { + h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; + resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); + if (my + HEIGHT(c) + m->gappx < m->wh) + my += HEIGHT(c) + m->gappx; + } else { + h = (m->wh - ty) / (n - i) - m->gappx; + resize(c, m->wx + mw + m->gappx, m->wy + ty, m->ww - mw - (2*c->bw) - 2*m->gappx, h - (2*c->bw), 0); + if (ty + HEIGHT(c) + m->gappx < m->wh) + ty += HEIGHT(c) + m->gappx; + } + + 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->pertag->showbars[selmon->pertag->curtag] = !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); + int i; + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + + if (newtagset == ~0) { + selmon->pertag->prevtag = selmon->pertag->curtag; + selmon->pertag->curtag = 0; + } + + /* test if the user did not select the same tag */ + if (!(newtagset & 1 << (selmon->pertag->curtag - 1))) { + selmon->pertag->prevtag = selmon->pertag->curtag; + for (i = 0; !(newtagset & 1 << i); i++) ; + selmon->pertag->curtag = i + 1; + } + + /* apply settings for this view */ + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; + selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; + selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; + + if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) + togglebar(NULL); + + 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() +{ + 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) +{ + char oldname[sizeof(c->name)]; + strcpy(oldname, c->name); + + 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); + + for (Monitor *m = mons; m; m = m->next) { + if (m->sel == c && strcmp(oldname, c->name) != 0) + ipc_focused_title_change_event(m->num, c->win, oldname, c->name); + } +} + +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 (c->isurgent) + XSetWindowBorder(dpy, c->win, scheme[SchemeUrg][ColBorder].pixel); + } + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} + +void +view(const Arg *arg) +{ + int i; + unsigned int tmptag; + + 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; + selmon->pertag->prevtag = selmon->pertag->curtag; + + if (arg->ui == ~0) + selmon->pertag->curtag = 0; + else { + for (i = 0; !(arg->ui & 1 << i); i++) ; + selmon->pertag->curtag = i + 1; + } + } else { + tmptag = selmon->pertag->prevtag; + selmon->pertag->prevtag = selmon->pertag->curtag; + selmon->pertag->curtag = tmptag; + } + + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; + selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; + selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; + + if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) + togglebar(NULL); + + 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(); + runautostart(); + run(); + if(restart) execvp(argv[0], argv); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/KleinDwm/source/dwm.c.orig b/KleinDwm/source/dwm.c.orig new file mode 100644 index 0000000..5907d42 --- /dev/null +++ b/KleinDwm/source/dwm.c.orig @@ -0,0 +1,2965 @@ +/* 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 +#include +#ifdef XINERAMA +#include +#endif /* XINERAMA */ +#include +#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, SchemeUrg, 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; + +typedef struct Pertag Pertag; +struct Monitor { + char ltsymbol[16]; + float mfact; + int nmaster; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; + int showbar; + int topbar; + Client *clients; + Client *sel; + Client *stack; + Monitor *next; + Window barwin; + const Layout *lt[2]; + Pertag *pertag; +}; + +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 Monitor *numtomon(int num); +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 focusnthmon(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 runautostart(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 tagnthmon(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 autostartblocksh[] = "autostart_blocking.sh"; +static const char autostartsh[] = "autostart.sh"; +static const char broken[] = "broken"; +static const char dwmdir[] = "dwm"; +static const char localshare[] = ".local/share"; +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" + +struct Pertag { + unsigned int curtag, prevtag; /* current and previous tag */ + int nmasters[LENGTH(tags) + 1]; /* number of windows in master area */ + float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */ + unsigned int sellts[LENGTH(tags) + 1]; /* selected layouts */ + const Layout *ltidxs[LENGTH(tags) + 1][2]; /* matrix of tags and layouts indexes */ + int showbars[LENGTH(tags) + 1]; /* display bar for the current tag */ +}; + +/* 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; + unsigned int i; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; + m->mfact = mfact; + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; + m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + m->pertag = ecalloc(1, sizeof(Pertag)); + m->pertag->curtag = m->pertag->prevtag = 1; + + for (i = 0; i <= LENGTH(tags); i++) { + m->pertag->nmasters[i] = m->nmaster; + m->pertag->mfacts[i] = m->mfact; + + m->pertag->ltidxs[i][0] = m->lt[0]; + m->pertag->ltidxs[i][1] = m->lt[1]; + m->pertag->sellts[i] = m->sellt; + + m->pertag->showbars[i] = m->showbar; + } + + 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; +} + +Monitor * +numtomon(int num) +{ + Monitor *m = NULL; + int i = 0; + + for(m = mons, i=0; m->next && i < num; m = m->next){ + i++; + } + return m; +} + +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 +focusnthmon(const Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + + if ((m = numtomon(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 = selmon->pertag->nmasters[selmon->pertag->curtag] = 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 +runautostart(void) +{ + char *pathpfx; + char *path; + char *xdgdatahome; + char *home; + struct stat sb; + + if ((home = getenv("HOME")) == NULL) + /* this is almost impossible */ + return; + + /* if $XDG_DATA_HOME is set and not empty, use $XDG_DATA_HOME/dwm, + * otherwise use ~/.local/share/dwm as autostart script directory + */ + xdgdatahome = getenv("XDG_DATA_HOME"); + if (xdgdatahome != NULL && *xdgdatahome != '\0') { + /* space for path segments, separators and nul */ + pathpfx = ecalloc(1, strlen(xdgdatahome) + strlen(dwmdir) + 2); + + if (sprintf(pathpfx, "%s/%s", xdgdatahome, dwmdir) <= 0) { + free(pathpfx); + return; + } + } else { + /* space for path segments, separators and nul */ + pathpfx = ecalloc(1, strlen(home) + strlen(localshare) + + strlen(dwmdir) + 3); + + if (sprintf(pathpfx, "%s/%s/%s", home, localshare, dwmdir) < 0) { + free(pathpfx); + return; + } + } + + /* check if the autostart script directory exists */ + if (! (stat(pathpfx, &sb) == 0 && S_ISDIR(sb.st_mode))) { + /* the XDG conformant path does not exist or is no directory + * so we try ~/.dwm instead + */ + char *pathpfx_new = realloc(pathpfx, strlen(home) + strlen(dwmdir) + 3); + if(pathpfx_new == NULL) { + free(pathpfx); + return; + } + pathpfx = pathpfx_new; + + if (sprintf(pathpfx, "%s/.%s", home, dwmdir) <= 0) { + free(pathpfx); + return; + } + } + + /* try the blocking script first */ + path = ecalloc(1, strlen(pathpfx) + strlen(autostartblocksh) + 2); + if (sprintf(path, "%s/%s", pathpfx, autostartblocksh) <= 0) { + free(path); + free(pathpfx); + } + + if (access(path, X_OK) == 0) + system(path); + + /* now the non-blocking script */ + if (sprintf(path, "%s/%s", pathpfx, autostartsh) <= 0) { + free(path); + free(pathpfx); + } + + if (access(path, X_OK) == 0) + system(strcat(path, " &")); + + free(pathpfx); + free(path); +} + +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 +setgaps(const Arg *arg) +{ + if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) + selmon->gappx = 0; + else + selmon->gappx += arg->i; + arrange(selmon); +} + +void +setlayout(const Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag] ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][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 = selmon->pertag->mfacts[selmon->pertag->curtag] = f; + arrange(selmon); +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + + /* clean up any zombies immediately */ + 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 = drw->fonts->h + user_bh; + 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) +{ + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + 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 +tagnthmon(const Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendmon(selmon->sel, numtomon(arg->i)); +} + +void +tile(Monitor *m) +{ + unsigned int i, n, h, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else + mw = m->ww - m->gappx; + for (i = 0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + if (i < m->nmaster) { + h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; + resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); + if (my + HEIGHT(c) + m->gappx < m->wh) + my += HEIGHT(c) + m->gappx; + } else { + h = (m->wh - ty) / (n - i) - m->gappx; + resize(c, m->wx + mw + m->gappx, m->wy + ty, m->ww - mw - (2*c->bw) - 2*m->gappx, h - (2*c->bw), 0); + if (ty + HEIGHT(c) + m->gappx < m->wh) + ty += HEIGHT(c) + m->gappx; + } + + 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->pertag->showbars[selmon->pertag->curtag] = !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); + int i; + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + + if (newtagset == ~0) { + selmon->pertag->prevtag = selmon->pertag->curtag; + selmon->pertag->curtag = 0; + } + + /* test if the user did not select the same tag */ + if (!(newtagset & 1 << (selmon->pertag->curtag - 1))) { + selmon->pertag->prevtag = selmon->pertag->curtag; + for (i = 0; !(newtagset & 1 << i); i++) ; + selmon->pertag->curtag = i + 1; + } + + /* apply settings for this view */ + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; + selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; + selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; + + if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) + togglebar(NULL); + + 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() +{ + 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 (c->isurgent) + XSetWindowBorder(dpy, c->win, scheme[SchemeUrg][ColBorder].pixel); + } + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} + +void +view(const Arg *arg) +{ + int i; + unsigned int tmptag; + + 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; + selmon->pertag->prevtag = selmon->pertag->curtag; + + if (arg->ui == ~0) + selmon->pertag->curtag = 0; + else { + for (i = 0; !(arg->ui & 1 << i); i++) ; + selmon->pertag->curtag = i + 1; + } + } else { + tmptag = selmon->pertag->prevtag; + selmon->pertag->prevtag = selmon->pertag->curtag; + selmon->pertag->curtag = tmptag; + } + + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; + selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; + selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; + + if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) + togglebar(NULL); + + 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(); + runautostart(); + run(); + if(restart) execvp(argv[0], argv); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/KleinDwm/source/dwm.c.rej b/KleinDwm/source/dwm.c.rej new file mode 100644 index 0000000..fdf33a2 --- /dev/null +++ b/KleinDwm/source/dwm.c.rej @@ -0,0 +1,53 @@ +--- dwm.c ++++ dwm.c +@@ -125,8 +139,10 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++ + struct Monitor { + char ltsymbol[16]; + float mfact; + int nmaster; + int num; +@@ -136,14 +152,17 @@ struct Monitor { + 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 { +@@ -278,17 +300,27 @@ static void (*handler[LASTEvent]) (XEvent *) = { + [UnmapNotify] = unmapnotify + }; + static Atom wmatom[WMLast], netatom[NetLast]; + static int running = 1; + static Cur *cursor[CurLast]; + static Clr **scheme; + static Display *dpy; + static Drw *drw; + static Window root, wmcheckwin; + + /* configuration, allows nested code to access above variables */ + #include "config.h" + + /* compile-time check if all tags fit into an unsigned int bit array. */ + struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; + +@@ -1677,8 +1774,37 @@ setup(void) + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(NULL); + } + + + void + seturgent(Client *c, int urg) diff --git a/KleinDwm/source/dwm.o b/KleinDwm/source/dwm.o new file mode 100644 index 0000000000000000000000000000000000000000..58d321566ccf2e8710866421684cac4bb842f034 GIT binary patch literal 128152 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 Rs2La-Kz9$r)PiVq{Q$P97SsR$ literal 0 HcmV?d00001 diff --git a/KleinDwm/source/fibonacci.c b/KleinDwm/source/fibonacci.c new file mode 100644 index 0000000..fce0a57 --- /dev/null +++ b/KleinDwm/source/fibonacci.c @@ -0,0 +1,66 @@ +void +fibonacci(Monitor *mon, int s) { + unsigned int i, n, nx, ny, nw, nh; + Client *c; + + for(n = 0, c = nexttiled(mon->clients); c; c = nexttiled(c->next), n++); + if(n == 0) + return; + + nx = mon->wx; + ny = 0; + nw = mon->ww; + nh = mon->wh; + + for(i = 0, c = nexttiled(mon->clients); c; c = nexttiled(c->next)) { + if((i % 2 && nh / 2 > 2 * c->bw) + || (!(i % 2) && nw / 2 > 2 * c->bw)) { + if(i < n - 1) { + if(i % 2) + nh /= 2; + else + nw /= 2; + if((i % 4) == 2 && !s) + nx += nw; + else if((i % 4) == 3 && !s) + ny += nh; + } + if((i % 4) == 0) { + if(s) + ny += nh; + else + ny -= nh; + } + else if((i % 4) == 1) + nx += nw; + else if((i % 4) == 2) + ny += nh; + else if((i % 4) == 3) { + if(s) + nx += nw; + else + nx -= nw; + } + if(i == 0) + { + if(n != 1) + nw = mon->ww * mon->mfact; + ny = mon->wy; + } + else if(i == 1) + nw = mon->ww - nw; + i++; + } + resize(c, nx, ny, nw - 2 * c->bw, nh - 2 * c->bw, False); + } +} + +void +dwindle(Monitor *mon) { + fibonacci(mon, 1); +} + +void +spiral(Monitor *mon) { + fibonacci(mon, 0); +} diff --git a/KleinDwm/source/ipc.c b/KleinDwm/source/ipc.c new file mode 100644 index 0000000..c404791 --- /dev/null +++ b/KleinDwm/source/ipc.c @@ -0,0 +1,1202 @@ +#include "ipc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "yajl_dumps.h" + +static struct sockaddr_un sockaddr; +static struct epoll_event sock_epoll_event; +static IPCClientList ipc_clients = NULL; +static int epoll_fd = -1; +static int sock_fd = -1; +static IPCCommand *ipc_commands; +static unsigned int ipc_commands_len; +// Max size is 1 MB +static const uint32_t MAX_MESSAGE_SIZE = 1000000; +static const int IPC_SOCKET_BACKLOG = 5; + +/** + * Create IPC socket at specified path and return file descriptor to socket. + * This initializes the static variable sockaddr. + */ +static int +ipc_create_socket(const char *filename) +{ + char *normal_filename; + char *parent; + const size_t addr_size = sizeof(struct sockaddr_un); + const int sock_type = SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC; + + normalizepath(filename, &normal_filename); + + // In case socket file exists + unlink(normal_filename); + + // For portability clear the addr structure, since some implementations have + // nonstandard fields in the structure + memset(&sockaddr, 0, addr_size); + + parentdir(normal_filename, &parent); + // Create parent directories + mkdirp(parent); + free(parent); + + sockaddr.sun_family = AF_LOCAL; + strcpy(sockaddr.sun_path, normal_filename); + free(normal_filename); + + sock_fd = socket(AF_LOCAL, sock_type, 0); + if (sock_fd == -1) { + fputs("Failed to create socket\n", stderr); + return -1; + } + + DEBUG("Created socket at %s\n", sockaddr.sun_path); + + if (bind(sock_fd, (const struct sockaddr *)&sockaddr, addr_size) == -1) { + fputs("Failed to bind socket\n", stderr); + return -1; + } + + DEBUG("Socket binded\n"); + + if (listen(sock_fd, IPC_SOCKET_BACKLOG) < 0) { + fputs("Failed to listen for connections on socket\n", stderr); + return -1; + } + + DEBUG("Now listening for connections on socket\n"); + + return sock_fd; +} + +/** + * Internal function used to receive IPC messages from a given file descriptor. + * + * Returns -1 on error reading (could be EAGAIN or EINTR) + * Returns -2 if EOF before header could be read + * Returns -3 if invalid IPC header + * Returns -4 if message length exceeds MAX_MESSAGE_SIZE + */ +static int +ipc_recv_message(int fd, uint8_t *msg_type, uint32_t *reply_size, + uint8_t **reply) +{ + uint32_t read_bytes = 0; + const int32_t to_read = sizeof(dwm_ipc_header_t); + char header[to_read]; + char *walk = header; + + // Try to read header + while (read_bytes < to_read) { + const ssize_t n = read(fd, header + read_bytes, to_read - read_bytes); + + if (n == 0) { + if (read_bytes == 0) { + fprintf(stderr, "Unexpectedly reached EOF while reading header."); + fprintf(stderr, + "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n", + read_bytes, to_read); + return -2; + } else { + fprintf(stderr, "Unexpectedly reached EOF while reading header."); + fprintf(stderr, + "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n", + read_bytes, to_read); + return -3; + } + } else if (n == -1) { + // errno will still be set + return -1; + } + + read_bytes += n; + } + + // Check if magic string in header matches + if (memcmp(walk, IPC_MAGIC, IPC_MAGIC_LEN) != 0) { + fprintf(stderr, "Invalid magic string. Got '%.*s', expected '%s'\n", + IPC_MAGIC_LEN, walk, IPC_MAGIC); + return -3; + } + + walk += IPC_MAGIC_LEN; + + // Extract reply size + memcpy(reply_size, walk, sizeof(uint32_t)); + walk += sizeof(uint32_t); + + if (*reply_size > MAX_MESSAGE_SIZE) { + fprintf(stderr, "Message too long: %" PRIu32 " bytes. ", *reply_size); + fprintf(stderr, "Maximum message size is: %d\n", MAX_MESSAGE_SIZE); + return -4; + } + + // Extract message type + memcpy(msg_type, walk, sizeof(uint8_t)); + walk += sizeof(uint8_t); + + if (*reply_size > 0) + (*reply) = malloc(*reply_size); + else + return 0; + + read_bytes = 0; + while (read_bytes < *reply_size) { + const ssize_t n = read(fd, *reply + read_bytes, *reply_size - read_bytes); + + if (n == 0) { + fprintf(stderr, "Unexpectedly reached EOF while reading payload."); + fprintf(stderr, "Read %" PRIu32 " bytes, expected %" PRIu32 " bytes.\n", + read_bytes, *reply_size); + free(*reply); + return -2; + } else if (n == -1) { + // TODO: Should we return and wait for another epoll event? + // This would require saving the partial read in some way. + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; + + free(*reply); + return -1; + } + + read_bytes += n; + } + + return 0; +} + +/** + * Internal function used to write a buffer to a file descriptor + * + * Returns number of bytes written if successful write + * Returns 0 if no bytes were written due to EAGAIN or EWOULDBLOCK + * Returns -1 on unknown error trying to write, errno will carry over from + * write() call + */ +static ssize_t +ipc_write_message(int fd, const void *buf, size_t count) +{ + size_t written = 0; + + while (written < count) { + const ssize_t n = write(fd, (uint8_t *)buf + written, count - written); + + if (n == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return written; + else if (errno == EINTR) + continue; + else + return n; + } + + written += n; + DEBUG("Wrote %zu/%zu to client at fd %d\n", written, count, fd); + } + + return written; +} + +/** + * Initialization for generic event message. This is used to allocate the yajl + * handle, set yajl options, and in the future any other initialization that + * should occur for event messages. + */ +static void +ipc_event_init_message(yajl_gen *gen) +{ + *gen = yajl_gen_alloc(NULL); + yajl_gen_config(*gen, yajl_gen_beautify, 1); +} + +/** + * Prepares buffers of IPC subscribers of specified event using buffer from yajl + * handle. + */ +static void +ipc_event_prepare_send_message(yajl_gen gen, IPCEvent event) +{ + const unsigned char *buffer; + size_t len = 0; + + yajl_gen_get_buf(gen, &buffer, &len); + len++; // For null char + + for (IPCClient *c = ipc_clients; c; c = c->next) { + if (c->subscriptions & event) { + DEBUG("Sending selected client change event to fd %d\n", c->fd); + ipc_prepare_send_message(c, IPC_TYPE_EVENT, len, (char *)buffer); + } + } + + // Not documented, but this frees temp_buffer + yajl_gen_free(gen); +} + +/** + * Initialization for generic reply message. This is used to allocate the yajl + * handle, set yajl options, and in the future any other initialization that + * should occur for reply messages. + */ +static void +ipc_reply_init_message(yajl_gen *gen) +{ + *gen = yajl_gen_alloc(NULL); + yajl_gen_config(*gen, yajl_gen_beautify, 1); +} + +/** + * Prepares the IPC client's buffer with a message using the buffer of the yajl + * handle. + */ +static void +ipc_reply_prepare_send_message(yajl_gen gen, IPCClient *c, + IPCMessageType msg_type) +{ + const unsigned char *buffer; + size_t len = 0; + + yajl_gen_get_buf(gen, &buffer, &len); + len++; // For null char + + ipc_prepare_send_message(c, msg_type, len, (const char *)buffer); + + // Not documented, but this frees temp_buffer + yajl_gen_free(gen); +} + +/** + * Find the IPCCommand with the specified name + * + * Returns 0 if a command with the specified name was found + * Returns -1 if a command with the specified name could not be found + */ +static int +ipc_get_ipc_command(const char *name, IPCCommand *ipc_command) +{ + for (int i = 0; i < ipc_commands_len; i++) { + if (strcmp(ipc_commands[i].name, name) == 0) { + *ipc_command = ipc_commands[i]; + return 0; + } + } + + return -1; +} + +/** + * Parse a IPC_TYPE_RUN_COMMAND message from a client. This function extracts + * the arguments, argument count, argument types, and command name and returns + * the parsed information as an IPCParsedCommand. If this function returns + * successfully, the parsed_command must be freed using + * ipc_free_parsed_command_members. + * + * Returns 0 if the message was successfully parsed + * Returns -1 otherwise + */ +static int +ipc_parse_run_command(char *msg, IPCParsedCommand *parsed_command) +{ + char error_buffer[1000]; + yajl_val parent = yajl_tree_parse(msg, error_buffer, 1000); + + if (parent == NULL) { + fputs("Failed to parse command from client\n", stderr); + fprintf(stderr, "%s\n", error_buffer); + fprintf(stderr, "Tried to parse: %s\n", msg); + return -1; + } + + // Format: + // { + // "command": "" + // "args": [ "arg1", "arg2", ... ] + // } + const char *command_path[] = {"command", 0}; + yajl_val command_val = yajl_tree_get(parent, command_path, yajl_t_string); + + if (command_val == NULL) { + fputs("No command key found in client message\n", stderr); + yajl_tree_free(parent); + return -1; + } + + const char *command_name = YAJL_GET_STRING(command_val); + size_t command_name_len = strlen(command_name); + parsed_command->name = (char *)malloc((command_name_len + 1) * sizeof(char)); + strcpy(parsed_command->name, command_name); + + DEBUG("Received command: %s\n", parsed_command->name); + + const char *args_path[] = {"args", 0}; + yajl_val args_val = yajl_tree_get(parent, args_path, yajl_t_array); + + if (args_val == NULL) { + fputs("No args key found in client message\n", stderr); + yajl_tree_free(parent); + return -1; + } + + unsigned int *argc = &parsed_command->argc; + Arg **args = &parsed_command->args; + ArgType **arg_types = &parsed_command->arg_types; + + *argc = args_val->u.array.len; + + // If no arguments are specified, make a dummy argument to pass to the + // function. This is just the way dwm's void(Arg*) functions are setup. + if (*argc == 0) { + *args = (Arg *)malloc(sizeof(Arg)); + *arg_types = (ArgType *)malloc(sizeof(ArgType)); + (*arg_types)[0] = ARG_TYPE_NONE; + (*args)[0].i = 0; + (*argc)++; + } else if (*argc > 0) { + *args = (Arg *)calloc(*argc, sizeof(Arg)); + *arg_types = (ArgType *)malloc(*argc * sizeof(ArgType)); + + for (int i = 0; i < *argc; i++) { + yajl_val arg_val = args_val->u.array.values[i]; + + if (YAJL_IS_NUMBER(arg_val)) { + if (YAJL_IS_INTEGER(arg_val)) { + // Any values below 0 must be a signed int + if (YAJL_GET_INTEGER(arg_val) < 0) { + (*args)[i].i = YAJL_GET_INTEGER(arg_val); + (*arg_types)[i] = ARG_TYPE_SINT; + DEBUG("i=%ld\n", (*args)[i].i); + // Any values above 0 should be an unsigned int + } else if (YAJL_GET_INTEGER(arg_val) >= 0) { + (*args)[i].ui = YAJL_GET_INTEGER(arg_val); + (*arg_types)[i] = ARG_TYPE_UINT; + DEBUG("ui=%ld\n", (*args)[i].i); + } + // If the number is not an integer, it must be a float + } else { + (*args)[i].f = (float)YAJL_GET_DOUBLE(arg_val); + (*arg_types)[i] = ARG_TYPE_FLOAT; + DEBUG("f=%f\n", (*args)[i].f); + // If argument is not a number, it must be a string + } + } else if (YAJL_IS_STRING(arg_val)) { + char *arg_s = YAJL_GET_STRING(arg_val); + size_t arg_s_size = (strlen(arg_s) + 1) * sizeof(char); + (*args)[i].v = (char *)malloc(arg_s_size); + (*arg_types)[i] = ARG_TYPE_STR; + strcpy((char *)(*args)[i].v, arg_s); + } + } + } + + yajl_tree_free(parent); + + return 0; +} + +/** + * Free the members of a IPCParsedCommand struct + */ +static void +ipc_free_parsed_command_members(IPCParsedCommand *command) +{ + for (int i = 0; i < command->argc; i++) { + if (command->arg_types[i] == ARG_TYPE_STR) free((void *)command->args[i].v); + } + free(command->args); + free(command->arg_types); + free(command->name); +} + +/** + * Check if the given arguments are the correct length and type. Also do any + * casting to correct the types. + * + * Returns 0 if the arguments were the correct length and types + * Returns -1 if the argument count doesn't match + * Returns -2 if the argument types don't match + */ +static int +ipc_validate_run_command(IPCParsedCommand *parsed, const IPCCommand actual) +{ + if (actual.argc != parsed->argc) return -1; + + for (int i = 0; i < parsed->argc; i++) { + ArgType ptype = parsed->arg_types[i]; + ArgType atype = actual.arg_types[i]; + + if (ptype != atype) { + if (ptype == ARG_TYPE_UINT && atype == ARG_TYPE_PTR) + // If this argument is supposed to be a void pointer, cast it + parsed->args[i].v = (void *)parsed->args[i].ui; + else if (ptype == ARG_TYPE_UINT && atype == ARG_TYPE_SINT) + // If this argument is supposed to be a signed int, cast it + parsed->args[i].i = parsed->args[i].ui; + else + return -2; + } + } + + return 0; +} + +/** + * Convert event name to their IPCEvent equivalent enum value + * + * Returns 0 if a valid event name was given + * Returns -1 otherwise + */ +static int +ipc_event_stoi(const char *subscription, IPCEvent *event) +{ + if (strcmp(subscription, "tag_change_event") == 0) + *event = IPC_EVENT_TAG_CHANGE; + else if (strcmp(subscription, "client_focus_change_event") == 0) + *event = IPC_EVENT_CLIENT_FOCUS_CHANGE; + else if (strcmp(subscription, "layout_change_event") == 0) + *event = IPC_EVENT_LAYOUT_CHANGE; + else if (strcmp(subscription, "monitor_focus_change_event") == 0) + *event = IPC_EVENT_MONITOR_FOCUS_CHANGE; + else if (strcmp(subscription, "focused_title_change_event") == 0) + *event = IPC_EVENT_FOCUSED_TITLE_CHANGE; + else if (strcmp(subscription, "focused_state_change_event") == 0) + *event = IPC_EVENT_FOCUSED_STATE_CHANGE; + else + return -1; + return 0; +} + +/** + * Parse a IPC_TYPE_SUBSCRIBE message from a client. This function extracts the + * event name and the subscription action from the message. + * + * Returns 0 if message was successfully parsed + * Returns -1 otherwise + */ +static int +ipc_parse_subscribe(const char *msg, IPCSubscriptionAction *subscribe, + IPCEvent *event) +{ + char error_buffer[100]; + yajl_val parent = yajl_tree_parse((char *)msg, error_buffer, 100); + + if (parent == NULL) { + fputs("Failed to parse command from client\n", stderr); + fprintf(stderr, "%s\n", error_buffer); + return -1; + } + + // Format: + // { + // "event": "" + // "action": "" + // } + const char *event_path[] = {"event", 0}; + yajl_val event_val = yajl_tree_get(parent, event_path, yajl_t_string); + + if (event_val == NULL) { + fputs("No 'event' key found in client message\n", stderr); + return -1; + } + + const char *event_str = YAJL_GET_STRING(event_val); + DEBUG("Received event: %s\n", event_str); + + if (ipc_event_stoi(event_str, event) < 0) return -1; + + const char *action_path[] = {"action", 0}; + yajl_val action_val = yajl_tree_get(parent, action_path, yajl_t_string); + + if (action_val == NULL) { + fputs("No 'action' key found in client message\n", stderr); + return -1; + } + + const char *action = YAJL_GET_STRING(action_val); + + if (strcmp(action, "subscribe") == 0) + *subscribe = IPC_ACTION_SUBSCRIBE; + else if (strcmp(action, "unsubscribe") == 0) + *subscribe = IPC_ACTION_UNSUBSCRIBE; + else { + fputs("Invalid action specified for subscription\n", stderr); + return -1; + } + + yajl_tree_free(parent); + + return 0; +} + +/** + * Parse an IPC_TYPE_GET_DWM_CLIENT message from a client. This function + * extracts the window id from the message. + * + * Returns 0 if message was successfully parsed + * Returns -1 otherwise + */ +static int +ipc_parse_get_dwm_client(const char *msg, Window *win) +{ + char error_buffer[100]; + + yajl_val parent = yajl_tree_parse(msg, error_buffer, 100); + + if (parent == NULL) { + fputs("Failed to parse message from client\n", stderr); + fprintf(stderr, "%s\n", error_buffer); + return -1; + } + + // Format: + // { + // "client_window_id": + // } + const char *win_path[] = {"client_window_id", 0}; + yajl_val win_val = yajl_tree_get(parent, win_path, yajl_t_number); + + if (win_val == NULL) { + fputs("No client window id found in client message\n", stderr); + return -1; + } + + *win = YAJL_GET_INTEGER(win_val); + + yajl_tree_free(parent); + + return 0; +} + +/** + * Called when an IPC_TYPE_RUN_COMMAND message is received from a client. This + * function parses, executes the given command, and prepares a reply message to + * the client indicating success/failure. + * + * NOTE: There is currently no check for argument validity beyond the number of + * arguments given and types of arguments. There is also no way to check if the + * function succeeded based on dwm's void(const Arg*) function types. Pointer + * arguments can cause crashes if they are not validated in the function itself. + * + * Returns 0 if message was successfully parsed + * Returns -1 on failure parsing message + */ +static int +ipc_run_command(IPCClient *ipc_client, char *msg) +{ + IPCParsedCommand parsed_command; + IPCCommand ipc_command; + + // Initialize struct + memset(&parsed_command, 0, sizeof(IPCParsedCommand)); + + if (ipc_parse_run_command(msg, &parsed_command) < 0) { + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND, + "Failed to parse run command"); + return -1; + } + + if (ipc_get_ipc_command(parsed_command.name, &ipc_command) < 0) { + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND, + "Command %s not found", parsed_command.name); + ipc_free_parsed_command_members(&parsed_command); + return -1; + } + + int res = ipc_validate_run_command(&parsed_command, ipc_command); + if (res < 0) { + if (res == -1) + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND, + "%u arguments provided, %u expected", + parsed_command.argc, ipc_command.argc); + else if (res == -2) + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND, + "Type mismatch"); + ipc_free_parsed_command_members(&parsed_command); + return -1; + } + + if (parsed_command.argc == 1) + ipc_command.func.single_param(parsed_command.args); + else if (parsed_command.argc > 1) + ipc_command.func.array_param(parsed_command.args, parsed_command.argc); + + DEBUG("Called function for command %s\n", parsed_command.name); + + ipc_free_parsed_command_members(&parsed_command); + + ipc_prepare_reply_success(ipc_client, IPC_TYPE_RUN_COMMAND); + return 0; +} + +/** + * Called when an IPC_TYPE_GET_MONITORS message is received from a client. It + * prepares a reply with the properties of all of the monitors in JSON. + */ +static void +ipc_get_monitors(IPCClient *c, Monitor *mons, Monitor *selmon) +{ + yajl_gen gen; + ipc_reply_init_message(&gen); + dump_monitors(gen, mons, selmon); + + ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_MONITORS); +} + +/** + * Called when an IPC_TYPE_GET_TAGS message is received from a client. It + * prepares a reply with info about all the tags in JSON. + */ +static void +ipc_get_tags(IPCClient *c, const char *tags[], const int tags_len) +{ + yajl_gen gen; + ipc_reply_init_message(&gen); + + dump_tags(gen, tags, tags_len); + + ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_TAGS); +} + +/** + * Called when an IPC_TYPE_GET_LAYOUTS message is received from a client. It + * prepares a reply with a JSON array of available layouts + */ +static void +ipc_get_layouts(IPCClient *c, const Layout layouts[], const int layouts_len) +{ + yajl_gen gen; + ipc_reply_init_message(&gen); + + dump_layouts(gen, layouts, layouts_len); + + ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_LAYOUTS); +} + +/** + * Called when an IPC_TYPE_GET_DWM_CLIENT message is received from a client. It + * prepares a JSON reply with the properties of the client with the specified + * window XID. + * + * Returns 0 if the message was successfully parsed and if the client with the + * specified window XID was found + * Returns -1 if the message could not be parsed + */ +static int +ipc_get_dwm_client(IPCClient *ipc_client, const char *msg, const Monitor *mons) +{ + Window win; + + if (ipc_parse_get_dwm_client(msg, &win) < 0) return -1; + + // Find client with specified window XID + for (const Monitor *m = mons; m; m = m->next) + for (Client *c = m->clients; c; c = c->next) + if (c->win == win) { + yajl_gen gen; + ipc_reply_init_message(&gen); + + dump_client(gen, c); + + ipc_reply_prepare_send_message(gen, ipc_client, + IPC_TYPE_GET_DWM_CLIENT); + + return 0; + } + + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_GET_DWM_CLIENT, + "Client with window id %d not found", win); + return -1; +} + +/** + * Called when an IPC_TYPE_SUBSCRIBE message is received from a client. It + * subscribes/unsubscribes the client from the specified event and replies with + * the result. + * + * Returns 0 if the message was successfully parsed. + * Returns -1 if the message could not be parsed + */ +static int +ipc_subscribe(IPCClient *c, const char *msg) +{ + IPCSubscriptionAction action = IPC_ACTION_SUBSCRIBE; + IPCEvent event = 0; + + if (ipc_parse_subscribe(msg, &action, &event)) { + ipc_prepare_reply_failure(c, IPC_TYPE_SUBSCRIBE, "Event does not exist"); + return -1; + } + + if (action == IPC_ACTION_SUBSCRIBE) { + DEBUG("Subscribing client on fd %d to %d\n", c->fd, event); + c->subscriptions |= event; + } else if (action == IPC_ACTION_UNSUBSCRIBE) { + DEBUG("Unsubscribing client on fd %d to %d\n", c->fd, event); + c->subscriptions ^= event; + } else { + ipc_prepare_reply_failure(c, IPC_TYPE_SUBSCRIBE, + "Invalid subscription action"); + return -1; + } + + ipc_prepare_reply_success(c, IPC_TYPE_SUBSCRIBE); + return 0; +} + +int +ipc_init(const char *socket_path, const int p_epoll_fd, IPCCommand commands[], + const int commands_len) +{ + // Initialize struct to 0 + memset(&sock_epoll_event, 0, sizeof(sock_epoll_event)); + + int socket_fd = ipc_create_socket(socket_path); + if (socket_fd < 0) return -1; + + ipc_commands = commands; + ipc_commands_len = commands_len; + + epoll_fd = p_epoll_fd; + + // Wake up to incoming connection requests + sock_epoll_event.data.fd = socket_fd; + sock_epoll_event.events = EPOLLIN; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &sock_epoll_event)) { + fputs("Failed to add sock file descriptor to epoll", stderr); + return -1; + } + + return socket_fd; +} + +void +ipc_cleanup() +{ + IPCClient *c = ipc_clients; + // Free clients and their buffers + while (c) { + ipc_drop_client(c); + c = ipc_clients; + } + + // Stop waking up for socket events + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock_fd, &sock_epoll_event); + + // Uninitialize all static variables + epoll_fd = -1; + sock_fd = -1; + ipc_commands = NULL; + ipc_commands_len = 0; + memset(&sock_epoll_event, 0, sizeof(struct epoll_event)); + memset(&sockaddr, 0, sizeof(struct sockaddr_un)); + + // Delete socket + unlink(sockaddr.sun_path); + + shutdown(sock_fd, SHUT_RDWR); + close(sock_fd); +} + +int +ipc_get_sock_fd() +{ + return sock_fd; +} + +IPCClient * +ipc_get_client(int fd) +{ + return ipc_list_get_client(ipc_clients, fd); +} + +int +ipc_is_client_registered(int fd) +{ + return (ipc_get_client(fd) != NULL); +} + +int +ipc_accept_client() +{ + int fd = -1; + + struct sockaddr_un client_addr; + socklen_t len = 0; + + // For portability clear the addr structure, since some implementations + // have nonstandard fields in the structure + memset(&client_addr, 0, sizeof(struct sockaddr_un)); + + fd = accept(sock_fd, (struct sockaddr *)&client_addr, &len); + if (fd < 0 && errno != EINTR) { + fputs("Failed to accept IPC connection from client", stderr); + return -1; + } + + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { + shutdown(fd, SHUT_RDWR); + close(fd); + fputs("Failed to set flags on new client fd", stderr); + } + + IPCClient *nc = ipc_client_new(fd); + if (nc == NULL) return -1; + + // Wake up to messages from this client + nc->event.data.fd = fd; + nc->event.events = EPOLLIN | EPOLLHUP; + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &nc->event); + + ipc_list_add_client(&ipc_clients, nc); + + DEBUG("%s%d\n", "New client at fd: ", fd); + + return fd; +} + +int +ipc_drop_client(IPCClient *c) +{ + int fd = c->fd; + shutdown(fd, SHUT_RDWR); + int res = close(fd); + + if (res == 0) { + struct epoll_event ev; + + // Stop waking up to messages from this client + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev); + ipc_list_remove_client(&ipc_clients, c); + + free(c->buffer); + free(c); + + DEBUG("Successfully removed client on fd %d\n", fd); + } else if (res < 0 && res != EINTR) { + fprintf(stderr, "Failed to close fd %d\n", fd); + } + + return res; +} + +int +ipc_read_client(IPCClient *c, IPCMessageType *msg_type, uint32_t *msg_size, + char **msg) +{ + int fd = c->fd; + int ret = + ipc_recv_message(fd, (uint8_t *)msg_type, msg_size, (uint8_t **)msg); + + if (ret < 0) { + // This will happen if these errors occur while reading header + if (ret == -1 && + (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) + return -2; + + fprintf(stderr, "Error reading message: dropping client at fd %d\n", fd); + ipc_drop_client(c); + + return -1; + } + + // Make sure receive message is null terminated to avoid parsing issues + if (*msg_size > 0) { + size_t len = *msg_size; + nullterminate(msg, &len); + *msg_size = len; + } + + DEBUG("[fd %d] ", fd); + if (*msg_size > 0) + DEBUG("Received message: '%.*s' ", *msg_size, *msg); + else + DEBUG("Received empty message "); + DEBUG("Message type: %" PRIu8 " ", (uint8_t)*msg_type); + DEBUG("Message size: %" PRIu32 "\n", *msg_size); + + return 0; +} + +ssize_t +ipc_write_client(IPCClient *c) +{ + const ssize_t n = ipc_write_message(c->fd, c->buffer, c->buffer_size); + + if (n < 0) return n; + + // TODO: Deal with client timeouts + + if (n == c->buffer_size) { + c->buffer_size = 0; + free(c->buffer); + // No dangling pointers! + c->buffer = NULL; + // Stop waking up when client is ready to receive messages + if (c->event.events & EPOLLOUT) { + c->event.events -= EPOLLOUT; + epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event); + } + return n; + } + + // Shift unwritten buffer to beginning of buffer and reallocate + c->buffer_size -= n; + memmove(c->buffer, c->buffer + n, c->buffer_size); + c->buffer = (char *)realloc(c->buffer, c->buffer_size); + + return n; +} + +void +ipc_prepare_send_message(IPCClient *c, const IPCMessageType msg_type, + const uint32_t msg_size, const char *msg) +{ + dwm_ipc_header_t header = { + .magic = IPC_MAGIC_ARR, .type = msg_type, .size = msg_size}; + + uint32_t header_size = sizeof(dwm_ipc_header_t); + uint32_t packet_size = header_size + msg_size; + + if (c->buffer == NULL) + c->buffer = (char *)malloc(c->buffer_size + packet_size); + else + c->buffer = (char *)realloc(c->buffer, c->buffer_size + packet_size); + + // Copy header to end of client buffer + memcpy(c->buffer + c->buffer_size, &header, header_size); + c->buffer_size += header_size; + + // Copy message to end of client buffer + memcpy(c->buffer + c->buffer_size, msg, msg_size); + c->buffer_size += msg_size; + + // Wake up when client is ready to receive messages + c->event.events |= EPOLLOUT; + epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event); +} + +void +ipc_prepare_reply_failure(IPCClient *c, IPCMessageType msg_type, + const char *format, ...) +{ + yajl_gen gen; + va_list args; + + // Get output size + va_start(args, format); + size_t len = vsnprintf(NULL, 0, format, args); + va_end(args); + char *buffer = (char *)malloc((len + 1) * sizeof(char)); + + ipc_reply_init_message(&gen); + + va_start(args, format); + vsnprintf(buffer, len + 1, format, args); + va_end(args); + dump_error_message(gen, buffer); + + ipc_reply_prepare_send_message(gen, c, msg_type); + fprintf(stderr, "[fd %d] Error: %s\n", c->fd, buffer); + + free(buffer); +} + +void +ipc_prepare_reply_success(IPCClient *c, IPCMessageType msg_type) +{ + const char *success_msg = "{\"result\":\"success\"}"; + const size_t msg_len = strlen(success_msg) + 1; // +1 for null char + + ipc_prepare_send_message(c, msg_type, msg_len, success_msg); +} + +void +ipc_tag_change_event(int mon_num, TagState old_state, TagState new_state) +{ + yajl_gen gen; + ipc_event_init_message(&gen); + dump_tag_event(gen, mon_num, old_state, new_state); + ipc_event_prepare_send_message(gen, IPC_EVENT_TAG_CHANGE); +} + +void +ipc_client_focus_change_event(int mon_num, Client *old_client, + Client *new_client) +{ + yajl_gen gen; + ipc_event_init_message(&gen); + dump_client_focus_change_event(gen, old_client, new_client, mon_num); + ipc_event_prepare_send_message(gen, IPC_EVENT_CLIENT_FOCUS_CHANGE); +} + +void +ipc_layout_change_event(const int mon_num, const char *old_symbol, + const Layout *old_layout, const char *new_symbol, + const Layout *new_layout) +{ + yajl_gen gen; + ipc_event_init_message(&gen); + dump_layout_change_event(gen, mon_num, old_symbol, old_layout, new_symbol, + new_layout); + ipc_event_prepare_send_message(gen, IPC_EVENT_LAYOUT_CHANGE); +} + +void +ipc_monitor_focus_change_event(const int last_mon_num, const int new_mon_num) +{ + yajl_gen gen; + ipc_event_init_message(&gen); + dump_monitor_focus_change_event(gen, last_mon_num, new_mon_num); + ipc_event_prepare_send_message(gen, IPC_EVENT_MONITOR_FOCUS_CHANGE); +} + +void +ipc_focused_title_change_event(const int mon_num, const Window client_id, + const char *old_name, const char *new_name) +{ + yajl_gen gen; + ipc_event_init_message(&gen); + dump_focused_title_change_event(gen, mon_num, client_id, old_name, new_name); + ipc_event_prepare_send_message(gen, IPC_EVENT_FOCUSED_TITLE_CHANGE); +} + +void +ipc_focused_state_change_event(const int mon_num, const Window client_id, + const ClientState *old_state, + const ClientState *new_state) +{ + yajl_gen gen; + ipc_event_init_message(&gen); + dump_focused_state_change_event(gen, mon_num, client_id, old_state, + new_state); + ipc_event_prepare_send_message(gen, IPC_EVENT_FOCUSED_STATE_CHANGE); +} + +void +ipc_send_events(Monitor *mons, Monitor **lastselmon, Monitor *selmon) +{ + for (Monitor *m = mons; m; m = m->next) { + unsigned int urg = 0, occ = 0, tagset = 0; + + for (Client *c = m->clients; c; c = c->next) { + occ |= c->tags; + + if (c->isurgent) urg |= c->tags; + } + tagset = m->tagset[m->seltags]; + + TagState new_state = {.selected = tagset, .occupied = occ, .urgent = urg}; + + if (memcmp(&m->tagstate, &new_state, sizeof(TagState)) != 0) { + ipc_tag_change_event(m->num, m->tagstate, new_state); + m->tagstate = new_state; + } + + if (m->lastsel != m->sel) { + ipc_client_focus_change_event(m->num, m->lastsel, m->sel); + m->lastsel = m->sel; + } + + if (strcmp(m->ltsymbol, m->lastltsymbol) != 0 || + m->lastlt != m->lt[m->sellt]) { + ipc_layout_change_event(m->num, m->lastltsymbol, m->lastlt, m->ltsymbol, + m->lt[m->sellt]); + strcpy(m->lastltsymbol, m->ltsymbol); + m->lastlt = m->lt[m->sellt]; + } + + if (*lastselmon != selmon) { + if (*lastselmon != NULL) + ipc_monitor_focus_change_event((*lastselmon)->num, selmon->num); + *lastselmon = selmon; + } + + Client *sel = m->sel; + if (!sel) continue; + ClientState *o = &m->sel->prevstate; + ClientState n = {.oldstate = sel->oldstate, + .isfixed = sel->isfixed, + .isfloating = sel->isfloating, + .isfullscreen = sel->isfullscreen, + .isurgent = sel->isurgent, + .neverfocus = sel->neverfocus}; + if (memcmp(o, &n, sizeof(ClientState)) != 0) { + ipc_focused_state_change_event(m->num, m->sel->win, o, &n); + *o = n; + } + } +} + +int +ipc_handle_client_epoll_event(struct epoll_event *ev, Monitor *mons, + Monitor **lastselmon, Monitor *selmon, + const char *tags[], const int tags_len, + const Layout *layouts, const int layouts_len) +{ + int fd = ev->data.fd; + IPCClient *c = ipc_get_client(fd); + + if (ev->events & EPOLLHUP) { + DEBUG("EPOLLHUP received from client at fd %d\n", fd); + ipc_drop_client(c); + } else if (ev->events & EPOLLOUT) { + DEBUG("Sending message to client at fd %d...\n", fd); + if (c->buffer_size) ipc_write_client(c); + } else if (ev->events & EPOLLIN) { + IPCMessageType msg_type = 0; + uint32_t msg_size = 0; + char *msg = NULL; + + DEBUG("Received message from fd %d\n", fd); + if (ipc_read_client(c, &msg_type, &msg_size, &msg) < 0) return -1; + + if (msg_type == IPC_TYPE_GET_MONITORS) + ipc_get_monitors(c, mons, selmon); + else if (msg_type == IPC_TYPE_GET_TAGS) + ipc_get_tags(c, tags, tags_len); + else if (msg_type == IPC_TYPE_GET_LAYOUTS) + ipc_get_layouts(c, layouts, layouts_len); + else if (msg_type == IPC_TYPE_RUN_COMMAND) { + if (ipc_run_command(c, msg) < 0) return -1; + ipc_send_events(mons, lastselmon, selmon); + } else if (msg_type == IPC_TYPE_GET_DWM_CLIENT) { + if (ipc_get_dwm_client(c, msg, mons) < 0) return -1; + } else if (msg_type == IPC_TYPE_SUBSCRIBE) { + if (ipc_subscribe(c, msg) < 0) return -1; + } else { + fprintf(stderr, "Invalid message type received from fd %d", fd); + ipc_prepare_reply_failure(c, msg_type, "Invalid message type: %d", + msg_type); + } + free(msg); + } else { + fprintf(stderr, "Epoll event returned %d from fd %d\n", ev->events, fd); + return -1; + } + + return 0; +} + +int +ipc_handle_socket_epoll_event(struct epoll_event *ev) +{ + if (!(ev->events & EPOLLIN)) return -1; + + // EPOLLIN means incoming client connection request + fputs("Received EPOLLIN event on socket\n", stderr); + int new_fd = ipc_accept_client(); + + return new_fd; +} diff --git a/KleinDwm/source/ipc.h b/KleinDwm/source/ipc.h new file mode 100644 index 0000000..e3b5bba --- /dev/null +++ b/KleinDwm/source/ipc.h @@ -0,0 +1,320 @@ +#ifndef IPC_H_ +#define IPC_H_ + +#include +#include +#include + +#include "IPCClient.h" + +// clang-format off +#define IPC_MAGIC "DWM-IPC" +#define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C'} +#define IPC_MAGIC_LEN 7 // Not including null char + +#define IPCCOMMAND(FUNC, ARGC, TYPES) \ + { #FUNC, {FUNC }, ARGC, (ArgType[ARGC])TYPES } +// clang-format on + +typedef enum IPCMessageType { + IPC_TYPE_RUN_COMMAND = 0, + IPC_TYPE_GET_MONITORS = 1, + IPC_TYPE_GET_TAGS = 2, + IPC_TYPE_GET_LAYOUTS = 3, + IPC_TYPE_GET_DWM_CLIENT = 4, + IPC_TYPE_SUBSCRIBE = 5, + IPC_TYPE_EVENT = 6 +} IPCMessageType; + +typedef enum IPCEvent { + IPC_EVENT_TAG_CHANGE = 1 << 0, + IPC_EVENT_CLIENT_FOCUS_CHANGE = 1 << 1, + IPC_EVENT_LAYOUT_CHANGE = 1 << 2, + IPC_EVENT_MONITOR_FOCUS_CHANGE = 1 << 3, + IPC_EVENT_FOCUSED_TITLE_CHANGE = 1 << 4, + IPC_EVENT_FOCUSED_STATE_CHANGE = 1 << 5 +} IPCEvent; + +typedef enum IPCSubscriptionAction { + IPC_ACTION_UNSUBSCRIBE = 0, + IPC_ACTION_SUBSCRIBE = 1 +} IPCSubscriptionAction; + +/** + * Every IPC packet starts with this structure + */ +typedef struct dwm_ipc_header { + uint8_t magic[IPC_MAGIC_LEN]; + uint32_t size; + uint8_t type; +} __attribute((packed)) dwm_ipc_header_t; + +typedef enum ArgType { + ARG_TYPE_NONE = 0, + ARG_TYPE_UINT = 1, + ARG_TYPE_SINT = 2, + ARG_TYPE_FLOAT = 3, + ARG_TYPE_PTR = 4, + ARG_TYPE_STR = 5 +} ArgType; + +/** + * An IPCCommand function can have either of these function signatures + */ +typedef union ArgFunction { + void (*single_param)(const Arg *); + void (*array_param)(const Arg *, int); +} ArgFunction; + +typedef struct IPCCommand { + char *name; + ArgFunction func; + unsigned int argc; + ArgType *arg_types; +} IPCCommand; + +typedef struct IPCParsedCommand { + char *name; + Arg *args; + ArgType *arg_types; + unsigned int argc; +} IPCParsedCommand; + +/** + * Initialize the IPC socket and the IPC module + * + * @param socket_path Path to create the socket at + * @param epoll_fd File descriptor for epoll + * @param commands Address of IPCCommands array defined in config.h + * @param commands_len Length of commands[] array + * + * @return int The file descriptor of the socket if it was successfully created, + * -1 otherwise + */ +int ipc_init(const char *socket_path, const int p_epoll_fd, + IPCCommand commands[], const int commands_len); + +/** + * Uninitialize the socket and module. Free allocated memory and restore static + * variables to their state before ipc_init + */ +void ipc_cleanup(); + +/** + * Get the file descriptor of the IPC socket + * + * @return int File descriptor of IPC socket, -1 if socket not created. + */ +int ipc_get_sock_fd(); + +/** + * Get address to IPCClient with specified file descriptor + * + * @param fd File descriptor of IPC Client + * + * @return Address to IPCClient with specified file descriptor, -1 otherwise + */ +IPCClient *ipc_get_client(int fd); + +/** + * Check if an IPC client exists with the specified file descriptor + * + * @param fd File descriptor + * + * @return int 1 if client exists, 0 otherwise + */ +int ipc_is_client_registered(int fd); + +/** + * Disconnect an IPCClient from the socket and remove the client from the list + * of known connected clients + * + * @param c Address of IPCClient + * + * @return 0 if the client's file descriptor was closed successfully, the + * result of executing close() on the file descriptor otherwise. + */ +int ipc_drop_client(IPCClient *c); + +/** + * Accept an IPC Client requesting to connect to the socket and add it to the + * list of clients + * + * @return File descriptor of new client, -1 on error + */ +int ipc_accept_client(); + +/** + * Read an incoming message from an accepted IPC client + * + * @param c Address of IPCClient + * @param msg_type Address to IPCMessageType variable which will be assigned + * the message type of the received message + * @param msg_size Address to uint32_t variable which will be assigned the size + * of the received message + * @param msg Address to char* variable which will be assigned the address of + * the received message. This must be freed using free(). + * + * @return 0 on success, -1 on error reading message, -2 if reading the message + * resulted in EAGAIN, EINTR, or EWOULDBLOCK. + */ +int ipc_read_client(IPCClient *c, IPCMessageType *msg_type, uint32_t *msg_size, + char **msg); + +/** + * Write any pending buffer of the client to the client's socket + * + * @param c Client whose buffer to write + * + * @return Number of bytes written >= 0, -1 otherwise. errno will still be set + * from the write operation. + */ +ssize_t ipc_write_client(IPCClient *c); + +/** + * Prepare a message in the specified client's buffer. + * + * @param c Client to prepare message for + * @param msg_type Type of message to prepare + * @param msg_size Size of the message in bytes. Should not exceed + * MAX_MESSAGE_SIZE + * @param msg Message to prepare (not including header). This pointer can be + * freed after the function invocation. + */ +void ipc_prepare_send_message(IPCClient *c, const IPCMessageType msg_type, + const uint32_t msg_size, const char *msg); + +/** + * Prepare an error message in the specified client's buffer + * + * @param c Client to prepare message for + * @param msg_type Type of message + * @param format Format string following vsprintf + * @param ... Arguments for format string + */ +void ipc_prepare_reply_failure(IPCClient *c, IPCMessageType msg_type, + const char *format, ...); + +/** + * Prepare a success message in the specified client's buffer + * + * @param c Client to prepare message for + * @param msg_type Type of message + */ +void ipc_prepare_reply_success(IPCClient *c, IPCMessageType msg_type); + +/** + * Send a tag_change_event to all subscribers. Should be called only when there + * has been a tag state change. + * + * @param mon_num The index of the monitor (Monitor.num property) + * @param old_state The old tag state + * @param new_state The new (now current) tag state + */ +void ipc_tag_change_event(const int mon_num, TagState old_state, + TagState new_state); + +/** + * Send a client_focus_change_event to all subscribers. Should be called only + * when the client focus changes. + * + * @param mon_num The index of the monitor (Monitor.num property) + * @param old_client The old DWM client selection (Monitor.oldsel) + * @param new_client The new (now current) DWM client selection + */ +void ipc_client_focus_change_event(const int mon_num, Client *old_client, + Client *new_client); + +/** + * Send a layout_change_event to all subscribers. Should be called only + * when there has been a layout change. + * + * @param mon_num The index of the monitor (Monitor.num property) + * @param old_symbol The old layout symbol + * @param old_layout Address to the old Layout + * @param new_symbol The new (now current) layout symbol + * @param new_layout Address to the new Layout + */ +void ipc_layout_change_event(const int mon_num, const char *old_symbol, + const Layout *old_layout, const char *new_symbol, + const Layout *new_layout); + +/** + * Send a monitor_focus_change_event to all subscribers. Should be called only + * when the monitor focus changes. + * + * @param last_mon_num The index of the previously selected monitor + * @param new_mon_num The index of the newly selected monitor + */ +void ipc_monitor_focus_change_event(const int last_mon_num, + const int new_mon_num); + +/** + * Send a focused_title_change_event to all subscribers. Should only be called + * if a selected client has a title change. + * + * @param mon_num Index of the client's monitor + * @param client_id Window XID of client + * @param old_name Old name of the client window + * @param new_name New name of the client window + */ +void ipc_focused_title_change_event(const int mon_num, const Window client_id, + const char *old_name, const char *new_name); + +/** + * Send a focused_state_change_event to all subscribers. Should only be called + * if a selected client has a state change. + * + * @param mon_num Index of the client's monitor + * @param client_id Window XID of client + * @param old_state Old state of the client + * @param new_state New state of the client + */ +void ipc_focused_state_change_event(const int mon_num, const Window client_id, + const ClientState *old_state, + const ClientState *new_state); +/** + * Check to see if an event has occured and call the *_change_event functions + * accordingly + * + * @param mons Address of Monitor pointing to start of linked list + * @param lastselmon Address of pointer to previously selected monitor + * @param selmon Address of selected Monitor + */ +void ipc_send_events(Monitor *mons, Monitor **lastselmon, Monitor *selmon); + +/** + * Handle an epoll event caused by a registered IPC client. Read, process, and + * handle any received messages from clients. Write pending buffer to client if + * the client is ready to receive messages. Drop clients that have sent an + * EPOLLHUP. + * + * @param ev Associated epoll event returned by epoll_wait + * @param mons Address of Monitor pointing to start of linked list + * @param selmon Address of selected Monitor + * @param lastselmon Address of pointer to previously selected monitor + * @param tags Array of tag names + * @param tags_len Length of tags array + * @param layouts Array of available layouts + * @param layouts_len Length of layouts array + * + * @return 0 if event was successfully handled, -1 on any error receiving + * or handling incoming messages or unhandled epoll event. + */ +int ipc_handle_client_epoll_event(struct epoll_event *ev, Monitor *mons, + Monitor **lastselmon, Monitor *selmon, + const char *tags[], const int tags_len, + const Layout *layouts, const int layouts_len); + +/** + * Handle an epoll event caused by the IPC socket. This function only handles an + * EPOLLIN event indicating a new client requesting to connect to the socket. + * + * @param ev Associated epoll event returned by epoll_wait + * + * @return 0, if the event was successfully handled, -1 if not an EPOLLIN event + * or if a new IPC client connection request could not be accepted. + */ +int ipc_handle_socket_epoll_event(struct epoll_event *ev); + +#endif /* IPC_H_ */ diff --git a/KleinDwm/source/keys.h b/KleinDwm/source/keys.h new file mode 100644 index 0000000..488a6b3 --- /dev/null +++ b/KleinDwm/source/keys.h @@ -0,0 +1,121 @@ +#include "layouts.h" +#include "movestack.c" +#include + +#define TERMINAL "alacritty" // 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} }, \ + { ALTMOD, CHAIN, KEY, focusnthmon, {.i = TAG } }, \ + { ALTMOD|ShiftMask, CHAIN, KEY, tagnthmon, {.i = 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 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 }; + + +/* brightness control */ +static const char *inc_light[] = { "brightness", "-a", "up", NULL}; +static const char *dec_light[] = { "brightness", "-a", "down", NULL }; + +/*xrandr control gui wrapper*/ +static const char *xrandrwrap[] = { "xrandrwrap", NULL }; +/* screenshot command */ +static const char *scrotselcmd[] = { "scrot", "-s", "/home/klein/Pictures/screenshots/Screenshot_%Y-%m-%d_%H-%M-%S.png", NULL }; + +/* volume controls */ +// Set up volume controls with amixer, and volumeicon. Without preset keys volumeicon will take over (provided constantly running) +// Do not add keys for XF86XK_AudioRaiseVolume, XF86XKK_AudioLowerVolume, or XF86KX_AudiotMute. +/* nvim */ +static const char *nvimcmd[] = { TERMINAL, "-e", "nvim", "vimwiki/index.md", 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|ShiftMask, -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|ShiftMask, -1, XK_x, movestack, {.i = +1 } }, + { MODKEY|ShiftMask, -1, XK_z, movestack, {.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_r, setlayout, {.v = &layouts[3]} }, + { MODKEY|ShiftMask, -1, XK_r, setlayout, {.v = &layouts[4]} }, + { 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 } }, +//--// + + { MODKEY, XK_c, XK_o, spawn, SHCMD("xdotool key Super_L+2 && dissent") }, + { MODKEY, XK_c, XK_q, spawn, SHCMD("xdotool key Super_L+2 && qtox") }, + { MODKEY, XK_c, XK_b, spawn, SHCMD("xdotool key Super_L+3 && qutebrowser") }, + { MODKEY, XK_c, XK_f, spawn, SHCMD("xdotool key Super_L+4 && freetube") }, + { MODKEY, XK_c, XK_g, spawn, SHCMD("xdotool key Super_L+5 && qutebrowser github.com") }, + { MODKEY, XK_c, XK_n, spawn, SHCMD("xdotool key Super_L+7 && nvim") }, + { MODKEY, XK_c, XK_s, spawn, SHCMD("xdotool key Super_L+8 && spotube") }, + { MODKEY, XK_c, XK_z, spawn, SHCMD("xdotool key Super_L+9 && alacritty") }, + { MODKEY, XK_n, XK_v, spawn, {.v = nvimcmd } }, + + 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_r, quit, {1} }, // restart + + { 0, -1, XK_F10, spawn, { .v = scrotselcmd } }, + { 0, -1, XF86XK_MonBrightnessUp, spawn, { .v = inc_light } }, + { 0, -1, XF86XK_MonBrightnessDown, spawn, { .v = dec_light } }, + { 0, -1, XK_F8, spawn, { .v = xrandrwrap } }, +}; + +/* 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/KleinDwm/source/layouts.h b/KleinDwm/source/layouts.h new file mode 100644 index 0000000..47425d5 --- /dev/null +++ b/KleinDwm/source/layouts.h @@ -0,0 +1,20 @@ +/* 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 */ + +#include "fibonacci.c" +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, + { "[@]", spiral }, + { "[\\]", dwindle }, +}; +/* 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/KleinDwm/source/movestack.c b/KleinDwm/source/movestack.c new file mode 100644 index 0000000..8d22a6b --- /dev/null +++ b/KleinDwm/source/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); + } +} diff --git a/KleinDwm/source/patches/README.md b/KleinDwm/source/patches/README.md new file mode 100644 index 0000000..a32fc57 --- /dev/null +++ b/KleinDwm/source/patches/README.md @@ -0,0 +1,103 @@ +## patches that I used on dwm + +### [autosart](https://dwm.suckless.org/patches/autostart/) + This patch allows programs to autostart when you login to dwm. + you can create autostart.sh file on the following directories: + 1. $HOME/.dwm + 2. $HOME/.local/share/dwm + 3. $XDG_DATA_HOME/dwm + +### [alwayscenter](https://dwm.suckless.org/patches/alwayscenter/) + This patch make all floating windows are centered. + +### [bar-height-spacing](https://dwm.suckless.org/patches/bar_height/) + This patch allows you to change dwm's default bar height. + ```c + static const int user_bh = 0; + ``` +### [fakefullscreen](https://dwm.suckless.org/patches/fakefullscreen/) + This patch enables fullscreen on a small secondary screen while having other windows. + + ![](screenshots/fake-fullscreen-patch.png) + +### [fullgabs](https://dwm.suckless.org/patches/fullgaps/) + This patch adds gaps between client windows. + ```c + static const unsigned int gappx = 5; /* gaps between windows */ + ``` + +### [keychain](https://dwm.suckless.org/patches/keychain/) + This patch allows you to chain multiple key bindings together. for example Mod+a+t to open alacritty terminal. + ```c + static const Key keys[] = { +/* modifier chain key key function argument */ + { MODKEY, XK_a, XK_t, spawn, SHCMD("alacritty") }, + } + ``` +### [movestack](https://dwm.suckless.org/patches/movestack/) +This patch allows you to move clients around in the stack and swap them with the main window by include movestack.c source file and add keys for swapping. + ```c + #include "movestack.c" + static const Key keys[] = { + { ShiftMask, -1, XK_x, movestack, {.i = +1 } }, + { ShiftMask, -1, XK_z, movestack, {.i = -1 } }, + } + ``` + +### [pertag](https://dwm.suckless.org/patches/pertag/) + This patch keeps layout, mwfact, barpos and nmaster per tag. + +### [raimbowtags](https://dwm.suckless.org/patches/rainbowtags/) + Allows each tag to be an independent colour when selected. + ```c + static const char tag1[] = "#939393"; + 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 *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 }, +}; + ``` + +### [resarting](https://dwm.suckless.org/patches/restartsig/) + This patch allows you to restart dwm. + +### [status2d-systray](https://dwm.suckless.org/patches/status2d/) + This patch allows colors in your dwm status bar with system tray implementation. + +### [titlecolor](https://dwm.suckless.org/patches/titlecolor/) + This patch adds a new color scheme used by the window title in the bar. + ```c + static const char *colors[][3] = { + [SchemeTitle] = { col_gray4, col_cyan, col_cyan }, + } + ``` + +### [urg-border](https://dwm.suckless.org/patches/urgentborder/) + This patch makes borders of urgent windows a different color. + + ```c + static const char col_urgborder[] = "#ff0000"; + static const char *colors[][3] = { + [SchemeUrg] = { col_gray4, col_cyan, col_urgborder }, + } + ``` + +### [winicon](https://dwm.suckless.org/patches/winicon/) + This patch enables dwm to show window icons. + + ![](screenshots/display-discord-icon.png) diff --git a/KleinDwm/source/patches/accessnthmon.diff b/KleinDwm/source/patches/accessnthmon.diff new file mode 100644 index 0000000..a65ec15 --- /dev/null +++ b/KleinDwm/source/patches/accessnthmon.diff @@ -0,0 +1,100 @@ +diff --git a/config.def.h b/config.def.h +index 1c0b587..8595a71 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -49,7 +49,10 @@ static const Layout layouts[] = { + { 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} }, ++ { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, \ ++ { ALTMOD, KEY, focusnthmon, {.i = TAG } }, \ ++ { ALTMOD|ShiftMask, KEY, tagnthmon, {.i = TAG } }, ++ + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +diff --git a/dwm.c b/dwm.c +index b0b3466..96fa0bd 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -161,6 +161,7 @@ static void destroynotify(XEvent *e); + static void detach(Client *c); + static void detachstack(Client *c); + static Monitor *dirtomon(int dir); ++static Monitor *numtomon(int num); + static void drawbar(Monitor *m); + static void drawbars(void); + static void enternotify(XEvent *e); +@@ -168,6 +169,7 @@ static void expose(XEvent *e); + static void focus(Client *c); + static void focusin(XEvent *e); + static void focusmon(const Arg *arg); ++static void focusnthmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Atom getatomprop(Client *c, Atom prop); + static int getrootptr(int *x, int *y); +@@ -209,6 +211,7 @@ static void sigchld(int unused); + static void spawn(const Arg *arg); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tagnthmon(const Arg *arg); + static void tile(Monitor *); + static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); +@@ -693,6 +696,18 @@ dirtomon(int dir) + return m; + } + ++Monitor * ++numtomon(int num) ++{ ++ Monitor *m = NULL; ++ int i = 0; ++ ++ for(m = mons, i=0; m->next && i < num; m = m->next){ ++ i++; ++ } ++ return m; ++} ++ + void + drawbar(Monitor *m) + { +@@ -830,6 +845,21 @@ focusmon(const Arg *arg) + focus(NULL); + } + ++void ++focusnthmon(const Arg *arg) ++{ ++ Monitor *m; ++ ++ if (!mons->next) ++ return; ++ ++ if ((m = numtomon(arg->i)) == selmon) ++ return; ++ unfocus(selmon->sel, 0); ++ selmon = m; ++ focus(NULL); ++} ++ + void + focusstack(const Arg *arg) + { +@@ -1671,6 +1701,14 @@ tagmon(const Arg *arg) + sendmon(selmon->sel, dirtomon(arg->i)); + } + ++void ++tagnthmon(const Arg *arg) ++{ ++ if (!selmon->sel || !mons->next) ++ return; ++ sendmon(selmon->sel, numtomon(arg->i)); ++} ++ + void + tile(Monitor *m) + { diff --git a/KleinDwm/source/patches/dwm-6.2-urg-border.diff b/KleinDwm/source/patches/dwm-6.2-urg-border.diff new file mode 100644 index 0000000..53f4d86 --- /dev/null +++ b/KleinDwm/source/patches/dwm-6.2-urg-border.diff @@ -0,0 +1,56 @@ +From f20e5593e154e7e46c3f7100bd1378c7844b5ec8 Mon Sep 17 00:00:00 2001 +From: Dirk Leichsenring +Date: Sun, 21 Jun 2020 14:00:40 +0200 +Subject: [PATCH] Make the borders of urgent windows a different color - for dwm 6.2 + +--- + config.def.h | 2 ++ + dwm.c | 7 +++++-- + 2 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1c0b587..1cb4492 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,10 +12,12 @@ 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 col_urgborder[] = "#ff0000"; + static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, ++ [SchemeUrg] = { col_gray4, col_cyan, col_urgborder }, + }; + + /* tagging */ +diff --git a/dwm.c b/dwm.c +index 4465af1..fda4013 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, SchemeUrg }; /* color schemes */ + enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ +@@ -2022,8 +2022,11 @@ updatewmhints(Client *c) + if (c == selmon->sel && wmh->flags & XUrgencyHint) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, c->win, wmh); +- } else ++ } else { + c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; ++ if (c->isurgent) ++ XSetWindowBorder(dpy, c->win, scheme[SchemeUrg][ColBorder].pixel); ++ } + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else +-- +2.27.0 + diff --git a/KleinDwm/source/patches/dwm-alwayscenter-20200625-f04cac6.diff b/KleinDwm/source/patches/dwm-alwayscenter-20200625-f04cac6.diff new file mode 100644 index 0000000..03ea9ef --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/patches/dwm-autostart-20210120-cb3f58a.diff b/KleinDwm/source/patches/dwm-autostart-20210120-cb3f58a.diff new file mode 100644 index 0000000..efee676 --- /dev/null +++ b/KleinDwm/source/patches/dwm-autostart-20210120-cb3f58a.diff @@ -0,0 +1,179 @@ +From 37e970479dc5d40e57fc0cbfeaa5e39941483237 Mon Sep 17 00:00:00 2001 +From: Gan Ainm +Date: Wed, 10 Jun 2020 10:59:02 +0000 +Subject: [PATCH] dwm-xdgautostart-6.2.diff + +=================================================================== +--- + dwm.1 | 23 +++++++++++++++++ + dwm.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 105 insertions(+) + +diff --git a/dwm.1 b/dwm.1 +index 13b3729..9533aa6 100644 +--- a/dwm.1 ++++ b/dwm.1 +@@ -30,6 +30,14 @@ top left corner. The tags which are applied to one or more windows are + indicated with an empty square in the top left corner. + .P + dwm draws a small border around windows to indicate the focus state. ++.P ++On start, dwm can start additional programs that may be specified in two special ++shell scripts (see the FILES section below), autostart_blocking.sh and ++autostart.sh. The former is executed first and dwm will wait for its ++termination before starting. The latter is executed in the background before ++dwm enters its handler loop. ++.P ++Either of these files may be omitted. + .SH OPTIONS + .TP + .B \-v +@@ -152,6 +160,21 @@ Toggles focused window between floating and tiled state. + .TP + .B Mod1\-Button3 + Resize focused window while dragging. Tiled windows will be toggled to the floating state. ++.SH FILES ++The files containing programs to be started along with dwm are searched for in ++the following directories: ++.IP "1. $XDG_DATA_HOME/dwm" ++.IP "2. $HOME/.local/share/dwm" ++.IP "3. $HOME/.dwm" ++.P ++The first existing directory is scanned for any of the autostart files below. ++.TP 15 ++autostart.sh ++This file is started as a shell background process before dwm enters its handler ++loop. ++.TP 15 ++autostart_blocking.sh ++This file is started before any autostart.sh; dwm waits for its termination. + .SH CUSTOMIZATION + dwm is customized by creating a custom config.h and (re)compiling the source + code. This keeps it fast, secure and simple. +diff --git a/dwm.c b/dwm.c +index 4465af1..2156b49 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -193,6 +194,7 @@ static void resizeclient(Client *c, int x, int y, int w, int h); + static void resizemouse(const Arg *arg); + static void restack(Monitor *m); + static void run(void); ++static void runautostart(void); + static void scan(void); + static int sendevent(Client *c, Atom proto); + static void sendmon(Client *c, Monitor *m); +@@ -235,7 +237,11 @@ static int xerrorstart(Display *dpy, XErrorEvent *ee); + static void zoom(const Arg *arg); + + /* variables */ ++static const char autostartblocksh[] = "autostart_blocking.sh"; ++static const char autostartsh[] = "autostart.sh"; + static const char broken[] = "broken"; ++static const char dwmdir[] = "dwm"; ++static const char localshare[] = ".local/share"; + static char stext[256]; + static int screen; + static int sw, sh; /* X display screen geometry width, height */ +@@ -1380,6 +1386,83 @@ run(void) + handler[ev.type](&ev); /* call handler */ + } + ++void ++runautostart(void) ++{ ++ char *pathpfx; ++ char *path; ++ char *xdgdatahome; ++ char *home; ++ struct stat sb; ++ ++ if ((home = getenv("HOME")) == NULL) ++ /* this is almost impossible */ ++ return; ++ ++ /* if $XDG_DATA_HOME is set and not empty, use $XDG_DATA_HOME/dwm, ++ * otherwise use ~/.local/share/dwm as autostart script directory ++ */ ++ xdgdatahome = getenv("XDG_DATA_HOME"); ++ if (xdgdatahome != NULL && *xdgdatahome != '\0') { ++ /* space for path segments, separators and nul */ ++ pathpfx = ecalloc(1, strlen(xdgdatahome) + strlen(dwmdir) + 2); ++ ++ if (sprintf(pathpfx, "%s/%s", xdgdatahome, dwmdir) <= 0) { ++ free(pathpfx); ++ return; ++ } ++ } else { ++ /* space for path segments, separators and nul */ ++ pathpfx = ecalloc(1, strlen(home) + strlen(localshare) ++ + strlen(dwmdir) + 3); ++ ++ if (sprintf(pathpfx, "%s/%s/%s", home, localshare, dwmdir) < 0) { ++ free(pathpfx); ++ return; ++ } ++ } ++ ++ /* check if the autostart script directory exists */ ++ if (! (stat(pathpfx, &sb) == 0 && S_ISDIR(sb.st_mode))) { ++ /* the XDG conformant path does not exist or is no directory ++ * so we try ~/.dwm instead ++ */ ++ char *pathpfx_new = realloc(pathpfx, strlen(home) + strlen(dwmdir) + 3); ++ if(pathpfx_new == NULL) { ++ free(pathpfx); ++ return; ++ } ++ pathpfx = pathpfx_new; ++ ++ if (sprintf(pathpfx, "%s/.%s", home, dwmdir) <= 0) { ++ free(pathpfx); ++ return; ++ } ++ } ++ ++ /* try the blocking script first */ ++ path = ecalloc(1, strlen(pathpfx) + strlen(autostartblocksh) + 2); ++ if (sprintf(path, "%s/%s", pathpfx, autostartblocksh) <= 0) { ++ free(path); ++ free(pathpfx); ++ } ++ ++ if (access(path, X_OK) == 0) ++ system(path); ++ ++ /* now the non-blocking script */ ++ if (sprintf(path, "%s/%s", pathpfx, autostartsh) <= 0) { ++ free(path); ++ free(pathpfx); ++ } ++ ++ if (access(path, X_OK) == 0) ++ system(strcat(path, " &")); ++ ++ free(pathpfx); ++ free(path); ++} ++ + void + scan(void) + { +@@ -2142,6 +2223,7 @@ main(int argc, char *argv[]) + die("pledge"); + #endif /* __OpenBSD__ */ + scan(); ++ runautostart(); + run(); + cleanup(); + XCloseDisplay(dpy); +-- +2.27.0 + diff --git a/KleinDwm/source/patches/dwm-bar-height-spacing-6.3.diff b/KleinDwm/source/patches/dwm-bar-height-spacing-6.3.diff new file mode 100644 index 0000000..cbdeb9a --- /dev/null +++ b/KleinDwm/source/patches/dwm-bar-height-spacing-6.3.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 = 2; /* 2 is the default spacing around the bar's font */ + 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 = drw->fonts->h + user_bh; + updategeom(); + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); diff --git a/KleinDwm/source/patches/dwm-centerfirstwindow-6.2.diff b/KleinDwm/source/patches/dwm-centerfirstwindow-6.2.diff new file mode 100644 index 0000000..707f2ec --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/patches/dwm-clientmonoclesymbol-20220417-d93ff48.diff b/KleinDwm/source/patches/dwm-clientmonoclesymbol-20220417-d93ff48.diff new file mode 100644 index 0000000..082461e --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/patches/dwm-fakefullscreen-20210714-138b405.diff b/KleinDwm/source/patches/dwm-fakefullscreen-20210714-138b405.diff new file mode 100644 index 0000000..88ca9f8 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/patches/dwm-fibonacci-6.2.diff b/KleinDwm/source/patches/dwm-fibonacci-6.2.diff new file mode 100644 index 0000000..81bba7a --- /dev/null +++ b/KleinDwm/source/patches/dwm-fibonacci-6.2.diff @@ -0,0 +1,114 @@ +From ec9f55b6005cfa3b025b3d700c61af3ce539d057 Mon Sep 17 00:00:00 2001 +From: Niki Yoshiuchi +Date: Sat, 18 Apr 2020 09:55:26 -0700 +Subject: [PATCH] Adding the fibonacci layout patch + +--- + config.def.h | 5 ++++ + fibonacci.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 71 insertions(+) + create mode 100644 fibonacci.c + +diff --git a/config.def.h b/config.def.h +index 1c0b587..5708487 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -36,11 +36,14 @@ 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 */ + ++#include "fibonacci.c" + static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "[@]", spiral }, ++ { "[\\]", dwindle }, + }; + + /* key definitions */ +@@ -76,6 +79,8 @@ static Key keys[] = { + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XK_r, setlayout, {.v = &layouts[3]} }, ++ { MODKEY|ShiftMask, XK_r, setlayout, {.v = &layouts[4]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, +diff --git a/fibonacci.c b/fibonacci.c +new file mode 100644 +index 0000000..fce0a57 +--- /dev/null ++++ b/fibonacci.c +@@ -0,0 +1,66 @@ ++void ++fibonacci(Monitor *mon, int s) { ++ unsigned int i, n, nx, ny, nw, nh; ++ Client *c; ++ ++ for(n = 0, c = nexttiled(mon->clients); c; c = nexttiled(c->next), n++); ++ if(n == 0) ++ return; ++ ++ nx = mon->wx; ++ ny = 0; ++ nw = mon->ww; ++ nh = mon->wh; ++ ++ for(i = 0, c = nexttiled(mon->clients); c; c = nexttiled(c->next)) { ++ if((i % 2 && nh / 2 > 2 * c->bw) ++ || (!(i % 2) && nw / 2 > 2 * c->bw)) { ++ if(i < n - 1) { ++ if(i % 2) ++ nh /= 2; ++ else ++ nw /= 2; ++ if((i % 4) == 2 && !s) ++ nx += nw; ++ else if((i % 4) == 3 && !s) ++ ny += nh; ++ } ++ if((i % 4) == 0) { ++ if(s) ++ ny += nh; ++ else ++ ny -= nh; ++ } ++ else if((i % 4) == 1) ++ nx += nw; ++ else if((i % 4) == 2) ++ ny += nh; ++ else if((i % 4) == 3) { ++ if(s) ++ nx += nw; ++ else ++ nx -= nw; ++ } ++ if(i == 0) ++ { ++ if(n != 1) ++ nw = mon->ww * mon->mfact; ++ ny = mon->wy; ++ } ++ else if(i == 1) ++ nw = mon->ww - nw; ++ i++; ++ } ++ resize(c, nx, ny, nw - 2 * c->bw, nh - 2 * c->bw, False); ++ } ++} ++ ++void ++dwindle(Monitor *mon) { ++ fibonacci(mon, 1); ++} ++ ++void ++spiral(Monitor *mon) { ++ fibonacci(mon, 0); ++} +-- +2.20.1 + diff --git a/KleinDwm/source/patches/dwm-fullgaps-20200508-7b77734.diff b/KleinDwm/source/patches/dwm-fullgaps-20200508-7b77734.diff new file mode 100644 index 0000000..368c871 --- /dev/null +++ b/KleinDwm/source/patches/dwm-fullgaps-20200508-7b77734.diff @@ -0,0 +1,138 @@ +From 7b7773458c072e4b24d6ea32d0364a8e402e4a43 Mon Sep 17 00:00:00 2001 +From: swy7ch +Date: Fri, 8 May 2020 19:07:24 +0200 +Subject: [PATCH] [PATCH] update dwm-fullgaps patch to be used with tile layout + update + +the recent tile layout changes in commit HEAD~1 (f09418b) broke the +patch + +this patch adapt the new `if` statements to take gaps into account + +this patch also provides manpage entries for the keybindings +--- + config.def.h | 4 ++++ + dwm.1 | 10 ++++++++++ + dwm.c | 33 +++++++++++++++++++++++---------- + 3 files changed, 37 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 1c0b587..38d2f6c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -2,6 +2,7 @@ + + /* appearance */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappx = 5; /* gaps between windows */ + static const unsigned int snap = 32; /* snap pixel */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ +@@ -84,6 +85,9 @@ static Key keys[] = { + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, ++ { MODKEY, XK_minus, setgaps, {.i = -1 } }, ++ { MODKEY, XK_equal, setgaps, {.i = +1 } }, ++ { MODKEY|ShiftMask, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) +diff --git a/dwm.1 b/dwm.1 +index 13b3729..0202d96 100644 +--- a/dwm.1 ++++ b/dwm.1 +@@ -140,6 +140,16 @@ View all windows with any tag. + .B Mod1\-Control\-[1..n] + Add/remove all windows with nth tag to/from the view. + .TP ++.B Mod1\-- ++Decrease the gaps around windows. ++.TP ++.B Mod1\-= ++Increase the gaps around windows. ++.TP ++.B Mod1\-Shift-= ++Reset the gaps around windows to ++.BR 0 . ++.TP + .B Mod1\-Shift\-q + Quit dwm. + .SS Mouse commands +diff --git a/dwm.c b/dwm.c +index 9fd0286..45a58f3 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -119,6 +119,7 @@ struct Monitor { + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ ++ int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; +@@ -200,6 +201,7 @@ static void sendmon(Client *c, Monitor *m); + static void setclientstate(Client *c, long state); + static void setfocus(Client *c); + static void setfullscreen(Client *c, int fullscreen); ++static void setgaps(const Arg *arg); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setup(void); +@@ -639,6 +641,7 @@ createmon(void) + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; ++ m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); +@@ -1498,6 +1501,16 @@ setfullscreen(Client *c, int fullscreen) + } + } + ++void ++setgaps(const Arg *arg) ++{ ++ if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) ++ selmon->gappx = 0; ++ else ++ selmon->gappx += arg->i; ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -1684,18 +1697,18 @@ tile(Monitor *m) + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else +- mw = m->ww; +- for (i = my = ty = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) ++ mw = m->ww - m->gappx; ++ for (i = 0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + if (i < m->nmaster) { +- h = (m->wh - my) / (MIN(n, m->nmaster) - i); +- resize(c, m->wx, m->wy + my, mw - (2*c->bw), h - (2*c->bw), 0); +- if (my + HEIGHT(c) < m->wh) +- my += HEIGHT(c); ++ h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; ++ resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); ++ if (my + HEIGHT(c) + m->gappx < m->wh) ++ my += HEIGHT(c) + m->gappx; + } else { +- h = (m->wh - ty) / (n - i); +- resize(c, m->wx + mw, m->wy + ty, m->ww - mw - (2*c->bw), h - (2*c->bw), 0); +- if (ty + HEIGHT(c) < m->wh) +- ty += HEIGHT(c); ++ h = (m->wh - ty) / (n - i) - m->gappx; ++ resize(c, m->wx + mw + m->gappx, m->wy + ty, m->ww - mw - (2*c->bw) - 2*m->gappx, h - (2*c->bw), 0); ++ if (ty + HEIGHT(c) + m->gappx < m->wh) ++ ty += HEIGHT(c) + m->gappx; + } + } + +-- +2.26.2 + diff --git a/KleinDwm/source/patches/dwm-ipc-20201106-f04cac6.diff b/KleinDwm/source/patches/dwm-ipc-20201106-f04cac6.diff new file mode 100644 index 0000000..5d947f6 --- /dev/null +++ b/KleinDwm/source/patches/dwm-ipc-20201106-f04cac6.diff @@ -0,0 +1,3246 @@ +From 9c4c16485ac374583a1055ff7c26cba53ac92c05 Mon Sep 17 00:00:00 2001 +From: mihirlad55 +Date: Fri, 6 Nov 2020 17:13:42 +0000 +Subject: [PATCH] Add IPC support through a unix socket + +This patch currently supports the following requests: +* Run custom commands with arguments (similar to key bind functions) +* Get monitor properties +* Get all available layouts +* Get available tags +* Get client properties +* Subscribe to tag change, client focus change, and layout change, + monitor focus change, focused title change, and client state change + events + +This patch includes a dwm-msg cli program that supports all of the +above requests for easy integration into shell scripts. + +The messages are sent in a JSON format to promote integration to +increase scriptability in languages like Python/JavaScript. + +The patch requires YAJL for JSON parsing and a system with epoll +support. Portability is planned to be increased in the future. + +This patch is best applied after all other patches to avoid merge +conflicts. + +For more info on the IPC implementation and how to send/receive +messages, documentation can be found at +https://github.com/mihirlad55/dwm-ipc +--- + IPCClient.c | 66 +++ + IPCClient.h | 61 +++ + Makefile | 10 +- + config.def.h | 18 + + config.mk | 8 +- + dwm-msg.c | 548 +++++++++++++++++++++++ + dwm.c | 150 ++++++- + ipc.c | 1202 ++++++++++++++++++++++++++++++++++++++++++++++++++ + ipc.h | 320 ++++++++++++++ + util.c | 135 ++++++ + util.h | 10 + + yajl_dumps.c | 351 +++++++++++++++ + yajl_dumps.h | 65 +++ + 13 files changed, 2931 insertions(+), 13 deletions(-) + create mode 100644 IPCClient.c + create mode 100644 IPCClient.h + create mode 100644 dwm-msg.c + create mode 100644 ipc.c + create mode 100644 ipc.h + create mode 100644 yajl_dumps.c + create mode 100644 yajl_dumps.h + +diff --git a/IPCClient.c b/IPCClient.c +new file mode 100644 +index 0000000..0d3eefb +--- /dev/null ++++ b/IPCClient.c +@@ -0,0 +1,66 @@ ++#include "IPCClient.h" ++ ++#include ++#include ++ ++#include "util.h" ++ ++IPCClient * ++ipc_client_new(int fd) ++{ ++ IPCClient *c = (IPCClient *)malloc(sizeof(IPCClient)); ++ ++ if (c == NULL) return NULL; ++ ++ // Initialize struct ++ memset(&c->event, 0, sizeof(struct epoll_event)); ++ ++ c->buffer_size = 0; ++ c->buffer = NULL; ++ c->fd = fd; ++ c->event.data.fd = fd; ++ c->next = NULL; ++ c->prev = NULL; ++ c->subscriptions = 0; ++ ++ return c; ++} ++ ++void ++ipc_list_add_client(IPCClientList *list, IPCClient *nc) ++{ ++ DEBUG("Adding client with fd %d to list\n", nc->fd); ++ ++ if (*list == NULL) { ++ // List is empty, point list at first client ++ *list = nc; ++ } else { ++ IPCClient *c; ++ // Go to last client in list ++ for (c = *list; c && c->next; c = c->next) ++ ; ++ c->next = nc; ++ nc->prev = c; ++ } ++} ++ ++void ++ipc_list_remove_client(IPCClientList *list, IPCClient *c) ++{ ++ IPCClient *cprev = c->prev; ++ IPCClient *cnext = c->next; ++ ++ if (cprev != NULL) cprev->next = c->next; ++ if (cnext != NULL) cnext->prev = c->prev; ++ if (c == *list) *list = c->next; ++} ++ ++IPCClient * ++ipc_list_get_client(IPCClientList list, int fd) ++{ ++ for (IPCClient *c = list; c; c = c->next) { ++ if (c->fd == fd) return c; ++ } ++ ++ return NULL; ++} +diff --git a/IPCClient.h b/IPCClient.h +new file mode 100644 +index 0000000..307dfba +--- /dev/null ++++ b/IPCClient.h +@@ -0,0 +1,61 @@ ++#ifndef IPC_CLIENT_H_ ++#define IPC_CLIENT_H_ ++ ++#include ++#include ++#include ++ ++typedef struct IPCClient IPCClient; ++/** ++ * This structure contains the details of an IPC Client and pointers for a ++ * linked list ++ */ ++struct IPCClient { ++ int fd; ++ int subscriptions; ++ ++ char *buffer; ++ uint32_t buffer_size; ++ ++ struct epoll_event event; ++ IPCClient *next; ++ IPCClient *prev; ++}; ++ ++typedef IPCClient *IPCClientList; ++ ++/** ++ * Allocate memory for new IPCClient with the specified file descriptor and ++ * initialize struct. ++ * ++ * @param fd File descriptor of IPC client ++ * ++ * @return Address to allocated IPCClient struct ++ */ ++IPCClient *ipc_client_new(int fd); ++ ++/** ++ * Add an IPC Client to the specified list ++ * ++ * @param list Address of the list to add the client to ++ * @param nc Address of the IPCClient ++ */ ++void ipc_list_add_client(IPCClientList *list, IPCClient *nc); ++ ++/** ++ * Remove an IPCClient from the specified list ++ * ++ * @param list Address of the list to remove the client from ++ * @param c Address of the IPCClient ++ */ ++void ipc_list_remove_client(IPCClientList *list, IPCClient *c); ++ ++/** ++ * Get an IPCClient from the specified IPCClient list ++ * ++ * @param list List to remove the client from ++ * @param fd File descriptor of the IPCClient ++ */ ++IPCClient *ipc_list_get_client(IPCClientList list, int fd); ++ ++#endif // IPC_CLIENT_H_ +diff --git a/Makefile b/Makefile +index 77bcbc0..0456754 100644 +--- a/Makefile ++++ b/Makefile +@@ -6,7 +6,7 @@ include config.mk + SRC = drw.c dwm.c util.c + OBJ = ${SRC:.c=.o} + +-all: options dwm ++all: options dwm dwm-msg + + options: + @echo dwm build options: +@@ -25,8 +25,11 @@ config.h: + dwm: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + ++dwm-msg: dwm-msg.o ++ ${CC} -o $@ $< ${LDFLAGS} ++ + clean: +- rm -f dwm ${OBJ} dwm-${VERSION}.tar.gz ++ rm -f dwm dwm-msg ${OBJ} dwm-${VERSION}.tar.gz + + dist: clean + mkdir -p dwm-${VERSION} +@@ -38,8 +41,9 @@ dist: clean + + install: all + mkdir -p ${DESTDIR}${PREFIX}/bin +- cp -f dwm ${DESTDIR}${PREFIX}/bin ++ cp -f dwm dwm-msg ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/dwm ++ chmod 755 ${DESTDIR}${PREFIX}/bin/dwm-msg + mkdir -p ${DESTDIR}${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1 +diff --git a/config.def.h b/config.def.h +index 1c0b587..059a831 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -113,3 +113,21 @@ static Button buttons[] = { + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, + }; + ++static const char *ipcsockpath = "/tmp/dwm.sock"; ++static IPCCommand ipccommands[] = { ++ IPCCOMMAND( view, 1, {ARG_TYPE_UINT} ), ++ IPCCOMMAND( toggleview, 1, {ARG_TYPE_UINT} ), ++ IPCCOMMAND( tag, 1, {ARG_TYPE_UINT} ), ++ IPCCOMMAND( toggletag, 1, {ARG_TYPE_UINT} ), ++ IPCCOMMAND( tagmon, 1, {ARG_TYPE_UINT} ), ++ IPCCOMMAND( focusmon, 1, {ARG_TYPE_SINT} ), ++ IPCCOMMAND( focusstack, 1, {ARG_TYPE_SINT} ), ++ IPCCOMMAND( zoom, 1, {ARG_TYPE_NONE} ), ++ IPCCOMMAND( incnmaster, 1, {ARG_TYPE_SINT} ), ++ IPCCOMMAND( killclient, 1, {ARG_TYPE_SINT} ), ++ IPCCOMMAND( togglefloating, 1, {ARG_TYPE_NONE} ), ++ IPCCOMMAND( setmfact, 1, {ARG_TYPE_FLOAT} ), ++ IPCCOMMAND( setlayoutsafe, 1, {ARG_TYPE_PTR} ), ++ IPCCOMMAND( quit, 1, {ARG_TYPE_NONE} ) ++}; ++ +diff --git a/config.mk b/config.mk +index 7084c33..8570938 100644 +--- a/config.mk ++++ b/config.mk +@@ -20,9 +20,13 @@ FREETYPEINC = /usr/include/freetype2 + # OpenBSD (uncomment) + #FREETYPEINC = ${X11INC}/freetype2 + ++# yajl ++YAJLLIBS = -lyajl ++YAJLINC = /usr/include/yajl ++ + # includes and libs +-INCS = -I${X11INC} -I${FREETYPEINC} +-LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} ++INCS = -I${X11INC} -I${FREETYPEINC} -I${YAJLINC} ++LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} ${YAJLLIBS} + + # flags + CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +diff --git a/dwm-msg.c b/dwm-msg.c +new file mode 100644 +index 0000000..1971d32 +--- /dev/null ++++ b/dwm-msg.c +@@ -0,0 +1,548 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define IPC_MAGIC "DWM-IPC" ++// clang-format off ++#define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C' } ++// clang-format on ++#define IPC_MAGIC_LEN 7 // Not including null char ++ ++#define IPC_EVENT_TAG_CHANGE "tag_change_event" ++#define IPC_EVENT_CLIENT_FOCUS_CHANGE "client_focus_change_event" ++#define IPC_EVENT_LAYOUT_CHANGE "layout_change_event" ++#define IPC_EVENT_MONITOR_FOCUS_CHANGE "monitor_focus_change_event" ++#define IPC_EVENT_FOCUSED_TITLE_CHANGE "focused_title_change_event" ++#define IPC_EVENT_FOCUSED_STATE_CHANGE "focused_state_change_event" ++ ++#define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str)) ++#define YINT(num) yajl_gen_integer(gen, num) ++#define YDOUBLE(num) yajl_gen_double(gen, num) ++#define YBOOL(v) yajl_gen_bool(gen, v) ++#define YNULL() yajl_gen_null(gen) ++#define YARR(body) \ ++ { \ ++ yajl_gen_array_open(gen); \ ++ body; \ ++ yajl_gen_array_close(gen); \ ++ } ++#define YMAP(body) \ ++ { \ ++ yajl_gen_map_open(gen); \ ++ body; \ ++ yajl_gen_map_close(gen); \ ++ } ++ ++typedef unsigned long Window; ++ ++const char *DEFAULT_SOCKET_PATH = "/tmp/dwm.sock"; ++static int sock_fd = -1; ++static unsigned int ignore_reply = 0; ++ ++typedef enum IPCMessageType { ++ IPC_TYPE_RUN_COMMAND = 0, ++ IPC_TYPE_GET_MONITORS = 1, ++ IPC_TYPE_GET_TAGS = 2, ++ IPC_TYPE_GET_LAYOUTS = 3, ++ IPC_TYPE_GET_DWM_CLIENT = 4, ++ IPC_TYPE_SUBSCRIBE = 5, ++ IPC_TYPE_EVENT = 6 ++} IPCMessageType; ++ ++// Every IPC message must begin with this ++typedef struct dwm_ipc_header { ++ uint8_t magic[IPC_MAGIC_LEN]; ++ uint32_t size; ++ uint8_t type; ++} __attribute((packed)) dwm_ipc_header_t; ++ ++static int ++recv_message(uint8_t *msg_type, uint32_t *reply_size, uint8_t **reply) ++{ ++ uint32_t read_bytes = 0; ++ const int32_t to_read = sizeof(dwm_ipc_header_t); ++ char header[to_read]; ++ char *walk = header; ++ ++ // Try to read header ++ while (read_bytes < to_read) { ++ ssize_t n = read(sock_fd, header + read_bytes, to_read - read_bytes); ++ ++ if (n == 0) { ++ if (read_bytes == 0) { ++ fprintf(stderr, "Unexpectedly reached EOF while reading header."); ++ fprintf(stderr, ++ "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n", ++ read_bytes, to_read); ++ return -2; ++ } else { ++ fprintf(stderr, "Unexpectedly reached EOF while reading header."); ++ fprintf(stderr, ++ "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n", ++ read_bytes, to_read); ++ return -3; ++ } ++ } else if (n == -1) { ++ return -1; ++ } ++ ++ read_bytes += n; ++ } ++ ++ // Check if magic string in header matches ++ if (memcmp(walk, IPC_MAGIC, IPC_MAGIC_LEN) != 0) { ++ fprintf(stderr, "Invalid magic string. Got '%.*s', expected '%s'\n", ++ IPC_MAGIC_LEN, walk, IPC_MAGIC); ++ return -3; ++ } ++ ++ walk += IPC_MAGIC_LEN; ++ ++ // Extract reply size ++ memcpy(reply_size, walk, sizeof(uint32_t)); ++ walk += sizeof(uint32_t); ++ ++ // Extract message type ++ memcpy(msg_type, walk, sizeof(uint8_t)); ++ walk += sizeof(uint8_t); ++ ++ (*reply) = malloc(*reply_size); ++ ++ // Extract payload ++ read_bytes = 0; ++ while (read_bytes < *reply_size) { ++ ssize_t n = read(sock_fd, *reply + read_bytes, *reply_size - read_bytes); ++ ++ if (n == 0) { ++ fprintf(stderr, "Unexpectedly reached EOF while reading payload."); ++ fprintf(stderr, "Read %" PRIu32 " bytes, expected %" PRIu32 " bytes.\n", ++ read_bytes, *reply_size); ++ free(*reply); ++ return -2; ++ } else if (n == -1) { ++ if (errno == EINTR || errno == EAGAIN) continue; ++ free(*reply); ++ return -1; ++ } ++ ++ read_bytes += n; ++ } ++ ++ return 0; ++} ++ ++static int ++read_socket(IPCMessageType *msg_type, uint32_t *msg_size, char **msg) ++{ ++ int ret = -1; ++ ++ while (ret != 0) { ++ ret = recv_message((uint8_t *)msg_type, msg_size, (uint8_t **)msg); ++ ++ if (ret < 0) { ++ // Try again (non-fatal error) ++ if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue; ++ ++ fprintf(stderr, "Error receiving response from socket. "); ++ fprintf(stderr, "The connection might have been lost.\n"); ++ exit(2); ++ } ++ } ++ ++ return 0; ++} ++ ++static ssize_t ++write_socket(const void *buf, size_t count) ++{ ++ size_t written = 0; ++ ++ while (written < count) { ++ const ssize_t n = ++ write(sock_fd, ((uint8_t *)buf) + written, count - written); ++ ++ if (n == -1) { ++ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) ++ continue; ++ else ++ return n; ++ } ++ written += n; ++ } ++ return written; ++} ++ ++static void ++connect_to_socket() ++{ ++ struct sockaddr_un addr; ++ ++ int sock = socket(AF_UNIX, SOCK_STREAM, 0); ++ ++ // Initialize struct to 0 ++ memset(&addr, 0, sizeof(struct sockaddr_un)); ++ ++ addr.sun_family = AF_UNIX; ++ strcpy(addr.sun_path, DEFAULT_SOCKET_PATH); ++ ++ connect(sock, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)); ++ ++ sock_fd = sock; ++} ++ ++static int ++send_message(IPCMessageType msg_type, uint32_t msg_size, uint8_t *msg) ++{ ++ dwm_ipc_header_t header = { ++ .magic = IPC_MAGIC_ARR, .size = msg_size, .type = msg_type}; ++ ++ size_t header_size = sizeof(dwm_ipc_header_t); ++ size_t total_size = header_size + msg_size; ++ ++ uint8_t buffer[total_size]; ++ ++ // Copy header to buffer ++ memcpy(buffer, &header, header_size); ++ // Copy message to buffer ++ memcpy(buffer + header_size, msg, header.size); ++ ++ write_socket(buffer, total_size); ++ ++ return 0; ++} ++ ++static int ++is_float(const char *s) ++{ ++ size_t len = strlen(s); ++ int is_dot_used = 0; ++ int is_minus_used = 0; ++ ++ // Floats can only have one decimal point in between or digits ++ // Optionally, floats can also be below zero (negative) ++ for (int i = 0; i < len; i++) { ++ if (isdigit(s[i])) ++ continue; ++ else if (!is_dot_used && s[i] == '.' && i != 0 && i != len - 1) { ++ is_dot_used = 1; ++ continue; ++ } else if (!is_minus_used && s[i] == '-' && i == 0) { ++ is_minus_used = 1; ++ continue; ++ } else ++ return 0; ++ } ++ ++ return 1; ++} ++ ++static int ++is_unsigned_int(const char *s) ++{ ++ size_t len = strlen(s); ++ ++ // Unsigned int can only have digits ++ for (int i = 0; i < len; i++) { ++ if (isdigit(s[i])) ++ continue; ++ else ++ return 0; ++ } ++ ++ return 1; ++} ++ ++static int ++is_signed_int(const char *s) ++{ ++ size_t len = strlen(s); ++ ++ // Signed int can only have digits and a negative sign at the start ++ for (int i = 0; i < len; i++) { ++ if (isdigit(s[i])) ++ continue; ++ else if (i == 0 && s[i] == '-') { ++ continue; ++ } else ++ return 0; ++ } ++ ++ return 1; ++} ++ ++static void ++flush_socket_reply() ++{ ++ IPCMessageType reply_type; ++ uint32_t reply_size; ++ char *reply; ++ ++ read_socket(&reply_type, &reply_size, &reply); ++ ++ free(reply); ++} ++ ++static void ++print_socket_reply() ++{ ++ IPCMessageType reply_type; ++ uint32_t reply_size; ++ char *reply; ++ ++ read_socket(&reply_type, &reply_size, &reply); ++ ++ printf("%.*s\n", reply_size, reply); ++ fflush(stdout); ++ free(reply); ++} ++ ++static int ++run_command(const char *name, char *args[], int argc) ++{ ++ const unsigned char *msg; ++ size_t msg_size; ++ ++ yajl_gen gen = yajl_gen_alloc(NULL); ++ ++ // Message format: ++ // { ++ // "command": "", ++ // "args": [ ... ] ++ // } ++ // clang-format off ++ YMAP( ++ YSTR("command"); YSTR(name); ++ YSTR("args"); YARR( ++ for (int i = 0; i < argc; i++) { ++ if (is_signed_int(args[i])) { ++ long long num = atoll(args[i]); ++ YINT(num); ++ } else if (is_float(args[i])) { ++ float num = atof(args[i]); ++ YDOUBLE(num); ++ } else { ++ YSTR(args[i]); ++ } ++ } ++ ) ++ ) ++ // clang-format on ++ ++ yajl_gen_get_buf(gen, &msg, &msg_size); ++ ++ send_message(IPC_TYPE_RUN_COMMAND, msg_size, (uint8_t *)msg); ++ ++ if (!ignore_reply) ++ print_socket_reply(); ++ else ++ flush_socket_reply(); ++ ++ yajl_gen_free(gen); ++ ++ return 0; ++} ++ ++static int ++get_monitors() ++{ ++ send_message(IPC_TYPE_GET_MONITORS, 1, (uint8_t *)""); ++ print_socket_reply(); ++ return 0; ++} ++ ++static int ++get_tags() ++{ ++ send_message(IPC_TYPE_GET_TAGS, 1, (uint8_t *)""); ++ print_socket_reply(); ++ ++ return 0; ++} ++ ++static int ++get_layouts() ++{ ++ send_message(IPC_TYPE_GET_LAYOUTS, 1, (uint8_t *)""); ++ print_socket_reply(); ++ ++ return 0; ++} ++ ++static int ++get_dwm_client(Window win) ++{ ++ const unsigned char *msg; ++ size_t msg_size; ++ ++ yajl_gen gen = yajl_gen_alloc(NULL); ++ ++ // Message format: ++ // { ++ // "client_window_id": "" ++ // } ++ // clang-format off ++ YMAP( ++ YSTR("client_window_id"); YINT(win); ++ ) ++ // clang-format on ++ ++ yajl_gen_get_buf(gen, &msg, &msg_size); ++ ++ send_message(IPC_TYPE_GET_DWM_CLIENT, msg_size, (uint8_t *)msg); ++ ++ print_socket_reply(); ++ ++ yajl_gen_free(gen); ++ ++ return 0; ++} ++ ++static int ++subscribe(const char *event) ++{ ++ const unsigned char *msg; ++ size_t msg_size; ++ ++ yajl_gen gen = yajl_gen_alloc(NULL); ++ ++ // Message format: ++ // { ++ // "event": "", ++ // "action": "subscribe" ++ // } ++ // clang-format off ++ YMAP( ++ YSTR("event"); YSTR(event); ++ YSTR("action"); YSTR("subscribe"); ++ ) ++ // clang-format on ++ ++ yajl_gen_get_buf(gen, &msg, &msg_size); ++ ++ send_message(IPC_TYPE_SUBSCRIBE, msg_size, (uint8_t *)msg); ++ ++ if (!ignore_reply) ++ print_socket_reply(); ++ else ++ flush_socket_reply(); ++ ++ yajl_gen_free(gen); ++ ++ return 0; ++} ++ ++static void ++usage_error(const char *prog_name, const char *format, ...) ++{ ++ va_list args; ++ va_start(args, format); ++ ++ fprintf(stderr, "Error: "); ++ vfprintf(stderr, format, args); ++ fprintf(stderr, "\nusage: %s [...]\n", prog_name); ++ fprintf(stderr, "Try '%s help'\n", prog_name); ++ ++ va_end(args); ++ exit(1); ++} ++ ++static void ++print_usage(const char *name) ++{ ++ printf("usage: %s [options] [...]\n", name); ++ puts(""); ++ puts("Commands:"); ++ puts(" run_command [args...] Run an IPC command"); ++ puts(""); ++ puts(" get_monitors Get monitor properties"); ++ puts(""); ++ puts(" get_tags Get list of tags"); ++ puts(""); ++ puts(" get_layouts Get list of layouts"); ++ puts(""); ++ puts(" get_dwm_client Get dwm client proprties"); ++ puts(""); ++ puts(" subscribe [events...] Subscribe to specified events"); ++ puts(" Options: " IPC_EVENT_TAG_CHANGE ","); ++ puts(" " IPC_EVENT_LAYOUT_CHANGE ","); ++ puts(" " IPC_EVENT_CLIENT_FOCUS_CHANGE ","); ++ puts(" " IPC_EVENT_MONITOR_FOCUS_CHANGE ","); ++ puts(" " IPC_EVENT_FOCUSED_TITLE_CHANGE ","); ++ puts(" " IPC_EVENT_FOCUSED_STATE_CHANGE); ++ puts(""); ++ puts(" help Display this message"); ++ puts(""); ++ puts("Options:"); ++ puts(" --ignore-reply Don't print reply messages from"); ++ puts(" run_command and subscribe."); ++ puts(""); ++} ++ ++int ++main(int argc, char *argv[]) ++{ ++ const char *prog_name = argv[0]; ++ ++ connect_to_socket(); ++ if (sock_fd == -1) { ++ fprintf(stderr, "Failed to connect to socket\n"); ++ return 1; ++ } ++ ++ int i = 1; ++ if (i < argc && strcmp(argv[i], "--ignore-reply") == 0) { ++ ignore_reply = 1; ++ i++; ++ } ++ ++ if (i >= argc) usage_error(prog_name, "Expected an argument, got none"); ++ ++ if (strcmp(argv[i], "help") == 0) ++ print_usage(prog_name); ++ else if (strcmp(argv[i], "run_command") == 0) { ++ if (++i >= argc) usage_error(prog_name, "No command specified"); ++ // Command name ++ char *command = argv[i]; ++ // Command arguments are everything after command name ++ char **command_args = argv + ++i; ++ // Number of command arguments ++ int command_argc = argc - i; ++ run_command(command, command_args, command_argc); ++ } else if (strcmp(argv[i], "get_monitors") == 0) { ++ get_monitors(); ++ } else if (strcmp(argv[i], "get_tags") == 0) { ++ get_tags(); ++ } else if (strcmp(argv[i], "get_layouts") == 0) { ++ get_layouts(); ++ } else if (strcmp(argv[i], "get_dwm_client") == 0) { ++ if (++i < argc) { ++ if (is_unsigned_int(argv[i])) { ++ Window win = atol(argv[i]); ++ get_dwm_client(win); ++ } else ++ usage_error(prog_name, "Expected unsigned integer argument"); ++ } else ++ usage_error(prog_name, "Expected the window id"); ++ } else if (strcmp(argv[i], "subscribe") == 0) { ++ if (++i < argc) { ++ for (int j = i; j < argc; j++) subscribe(argv[j]); ++ } else ++ usage_error(prog_name, "Expected event name"); ++ // Keep listening for events forever ++ while (1) { ++ print_socket_reply(); ++ } ++ } else ++ usage_error(prog_name, "Invalid argument '%s'", argv[i]); ++ ++ return 0; ++} +diff --git a/dwm.c b/dwm.c +index 9fd0286..c90c61a 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -30,6 +30,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -67,9 +68,21 @@ enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms * + enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ + ++typedef struct TagState TagState; ++struct TagState { ++ int selected; ++ int occupied; ++ int urgent; ++}; ++ ++typedef struct ClientState ClientState; ++struct ClientState { ++ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; ++}; ++ + typedef union { +- int i; +- unsigned int ui; ++ long i; ++ unsigned long ui; + float f; + const void *v; + } Arg; +@@ -97,6 +110,7 @@ struct Client { + Client *snext; + Monitor *mon; + Window win; ++ ClientState prevstate; + }; + + typedef struct { +@@ -111,8 +125,10 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++ + struct Monitor { + char ltsymbol[16]; ++ char lastltsymbol[16]; + float mfact; + int nmaster; + int num; +@@ -122,14 +138,17 @@ struct Monitor { + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; ++ TagState tagstate; + int showbar; + int topbar; + Client *clients; + Client *sel; ++ Client *lastsel; + Client *stack; + Monitor *next; + Window barwin; + const Layout *lt[2]; ++ const Layout *lastlt; + }; + + typedef struct { +@@ -175,6 +194,7 @@ static long getstate(Window w); + static int gettextprop(Window w, Atom atom, char *text, unsigned int size); + static void grabbuttons(Client *c, int focused); + static void grabkeys(void); ++static int handlexevent(struct epoll_event *ev); + static void incnmaster(const Arg *arg); + static void keypress(XEvent *e); + static void killclient(const Arg *arg); +@@ -201,8 +221,10 @@ 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 setlayoutsafe(const Arg *arg); + static void setmfact(const Arg *arg); + static void setup(void); ++static void setupepoll(void); + static void seturgent(Client *c, int urg); + static void showhide(Client *c); + static void sigchld(int unused); +@@ -261,17 +283,27 @@ static void (*handler[LASTEvent]) (XEvent *) = { + [UnmapNotify] = unmapnotify + }; + static Atom wmatom[WMLast], netatom[NetLast]; ++static int epoll_fd; ++static int dpy_fd; + static int running = 1; + static Cur *cursor[CurLast]; + static Clr **scheme; + static Display *dpy; + static Drw *drw; +-static Monitor *mons, *selmon; ++static Monitor *mons, *selmon, *lastselmon; + static Window root, wmcheckwin; + ++#include "ipc.h" ++ + /* configuration, allows nested code to access above variables */ + #include "config.h" + ++#ifdef VERSION ++#include "IPCClient.c" ++#include "yajl_dumps.c" ++#include "ipc.c" ++#endif ++ + /* compile-time check if all tags fit into an unsigned int bit array. */ + struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; + +@@ -492,6 +524,12 @@ cleanup(void) + XSync(dpy, False); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); ++ ++ ipc_cleanup(); ++ ++ if (close(epoll_fd) < 0) { ++ fprintf(stderr, "Failed to close epoll file descriptor\n"); ++ } + } + + void +@@ -964,6 +1002,25 @@ grabkeys(void) + } + } + ++int ++handlexevent(struct epoll_event *ev) ++{ ++ if (ev->events & EPOLLIN) { ++ XEvent ev; ++ while (running && XPending(dpy)) { ++ XNextEvent(dpy, &ev); ++ if (handler[ev.type]) { ++ handler[ev.type](&ev); /* call handler */ ++ ipc_send_events(mons, &lastselmon, selmon); ++ } ++ } ++ } else if (ev-> events & EPOLLHUP) { ++ return -1; ++ } ++ ++ return 0; ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1373,12 +1430,40 @@ restack(Monitor *m) + void + run(void) + { +- XEvent ev; +- /* main event loop */ ++ int event_count = 0; ++ const int MAX_EVENTS = 10; ++ struct epoll_event events[MAX_EVENTS]; ++ + XSync(dpy, False); +- while (running && !XNextEvent(dpy, &ev)) +- if (handler[ev.type]) +- handler[ev.type](&ev); /* call handler */ ++ ++ /* main event loop */ ++ while (running) { ++ event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); ++ ++ for (int i = 0; i < event_count; i++) { ++ int event_fd = events[i].data.fd; ++ DEBUG("Got event from fd %d\n", event_fd); ++ ++ if (event_fd == dpy_fd) { ++ // -1 means EPOLLHUP ++ if (handlexevent(events + i) == -1) ++ return; ++ } else if (event_fd == ipc_get_sock_fd()) { ++ ipc_handle_socket_epoll_event(events + i); ++ } else if (ipc_is_client_registered(event_fd)){ ++ if (ipc_handle_client_epoll_event(events + i, mons, &lastselmon, selmon, ++ tags, LENGTH(tags), layouts, LENGTH(layouts)) < 0) { ++ fprintf(stderr, "Error handling IPC event on fd %d\n", event_fd); ++ } ++ } else { ++ fprintf(stderr, "Got event from unknown fd %d, ptr %p, u32 %d, u64 %lu", ++ event_fd, events[i].data.ptr, events[i].data.u32, ++ events[i].data.u64); ++ fprintf(stderr, " with events %d\n", events[i].events); ++ return; ++ } ++ } ++ } + } + + void +@@ -1512,6 +1597,18 @@ setlayout(const Arg *arg) + drawbar(selmon); + } + ++void ++setlayoutsafe(const Arg *arg) ++{ ++ const Layout *ltptr = (Layout *)arg->v; ++ if (ltptr == 0) ++ setlayout(arg); ++ for (int i = 0; i < LENGTH(layouts); i++) { ++ if (ltptr == &layouts[i]) ++ setlayout(arg); ++ } ++} ++ + /* arg > 1.0 will set mfact absolutely */ + void + setmfact(const Arg *arg) +@@ -1595,8 +1692,37 @@ setup(void) + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(NULL); ++ setupepoll(); + } + ++void ++setupepoll(void) ++{ ++ epoll_fd = epoll_create1(0); ++ dpy_fd = ConnectionNumber(dpy); ++ struct epoll_event dpy_event; ++ ++ // Initialize struct to 0 ++ memset(&dpy_event, 0, sizeof(dpy_event)); ++ ++ DEBUG("Display socket is fd %d\n", dpy_fd); ++ ++ if (epoll_fd == -1) { ++ fputs("Failed to create epoll file descriptor", stderr); ++ } ++ ++ dpy_event.events = EPOLLIN; ++ dpy_event.data.fd = dpy_fd; ++ if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, dpy_fd, &dpy_event)) { ++ fputs("Failed to add display file descriptor to epoll", stderr); ++ close(epoll_fd); ++ exit(1); ++ } ++ ++ if (ipc_init(ipcsockpath, epoll_fd, ipccommands, LENGTH(ipccommands)) < 0) { ++ fputs("Failed to initialize IPC\n", stderr); ++ } ++} + + void + seturgent(Client *c, int urg) +@@ -1998,10 +2124,18 @@ updatestatus(void) + void + updatetitle(Client *c) + { ++ char oldname[sizeof(c->name)]; ++ strcpy(oldname, c->name); ++ + 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); ++ ++ for (Monitor *m = mons; m; m = m->next) { ++ if (m->sel == c && strcmp(oldname, c->name) != 0) ++ ipc_focused_title_change_event(m->num, c->win, oldname, c->name); ++ } + } + + void +diff --git a/ipc.c b/ipc.c +new file mode 100644 +index 0000000..c404791 +--- /dev/null ++++ b/ipc.c +@@ -0,0 +1,1202 @@ ++#include "ipc.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "util.h" ++#include "yajl_dumps.h" ++ ++static struct sockaddr_un sockaddr; ++static struct epoll_event sock_epoll_event; ++static IPCClientList ipc_clients = NULL; ++static int epoll_fd = -1; ++static int sock_fd = -1; ++static IPCCommand *ipc_commands; ++static unsigned int ipc_commands_len; ++// Max size is 1 MB ++static const uint32_t MAX_MESSAGE_SIZE = 1000000; ++static const int IPC_SOCKET_BACKLOG = 5; ++ ++/** ++ * Create IPC socket at specified path and return file descriptor to socket. ++ * This initializes the static variable sockaddr. ++ */ ++static int ++ipc_create_socket(const char *filename) ++{ ++ char *normal_filename; ++ char *parent; ++ const size_t addr_size = sizeof(struct sockaddr_un); ++ const int sock_type = SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC; ++ ++ normalizepath(filename, &normal_filename); ++ ++ // In case socket file exists ++ unlink(normal_filename); ++ ++ // For portability clear the addr structure, since some implementations have ++ // nonstandard fields in the structure ++ memset(&sockaddr, 0, addr_size); ++ ++ parentdir(normal_filename, &parent); ++ // Create parent directories ++ mkdirp(parent); ++ free(parent); ++ ++ sockaddr.sun_family = AF_LOCAL; ++ strcpy(sockaddr.sun_path, normal_filename); ++ free(normal_filename); ++ ++ sock_fd = socket(AF_LOCAL, sock_type, 0); ++ if (sock_fd == -1) { ++ fputs("Failed to create socket\n", stderr); ++ return -1; ++ } ++ ++ DEBUG("Created socket at %s\n", sockaddr.sun_path); ++ ++ if (bind(sock_fd, (const struct sockaddr *)&sockaddr, addr_size) == -1) { ++ fputs("Failed to bind socket\n", stderr); ++ return -1; ++ } ++ ++ DEBUG("Socket binded\n"); ++ ++ if (listen(sock_fd, IPC_SOCKET_BACKLOG) < 0) { ++ fputs("Failed to listen for connections on socket\n", stderr); ++ return -1; ++ } ++ ++ DEBUG("Now listening for connections on socket\n"); ++ ++ return sock_fd; ++} ++ ++/** ++ * Internal function used to receive IPC messages from a given file descriptor. ++ * ++ * Returns -1 on error reading (could be EAGAIN or EINTR) ++ * Returns -2 if EOF before header could be read ++ * Returns -3 if invalid IPC header ++ * Returns -4 if message length exceeds MAX_MESSAGE_SIZE ++ */ ++static int ++ipc_recv_message(int fd, uint8_t *msg_type, uint32_t *reply_size, ++ uint8_t **reply) ++{ ++ uint32_t read_bytes = 0; ++ const int32_t to_read = sizeof(dwm_ipc_header_t); ++ char header[to_read]; ++ char *walk = header; ++ ++ // Try to read header ++ while (read_bytes < to_read) { ++ const ssize_t n = read(fd, header + read_bytes, to_read - read_bytes); ++ ++ if (n == 0) { ++ if (read_bytes == 0) { ++ fprintf(stderr, "Unexpectedly reached EOF while reading header."); ++ fprintf(stderr, ++ "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n", ++ read_bytes, to_read); ++ return -2; ++ } else { ++ fprintf(stderr, "Unexpectedly reached EOF while reading header."); ++ fprintf(stderr, ++ "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n", ++ read_bytes, to_read); ++ return -3; ++ } ++ } else if (n == -1) { ++ // errno will still be set ++ return -1; ++ } ++ ++ read_bytes += n; ++ } ++ ++ // Check if magic string in header matches ++ if (memcmp(walk, IPC_MAGIC, IPC_MAGIC_LEN) != 0) { ++ fprintf(stderr, "Invalid magic string. Got '%.*s', expected '%s'\n", ++ IPC_MAGIC_LEN, walk, IPC_MAGIC); ++ return -3; ++ } ++ ++ walk += IPC_MAGIC_LEN; ++ ++ // Extract reply size ++ memcpy(reply_size, walk, sizeof(uint32_t)); ++ walk += sizeof(uint32_t); ++ ++ if (*reply_size > MAX_MESSAGE_SIZE) { ++ fprintf(stderr, "Message too long: %" PRIu32 " bytes. ", *reply_size); ++ fprintf(stderr, "Maximum message size is: %d\n", MAX_MESSAGE_SIZE); ++ return -4; ++ } ++ ++ // Extract message type ++ memcpy(msg_type, walk, sizeof(uint8_t)); ++ walk += sizeof(uint8_t); ++ ++ if (*reply_size > 0) ++ (*reply) = malloc(*reply_size); ++ else ++ return 0; ++ ++ read_bytes = 0; ++ while (read_bytes < *reply_size) { ++ const ssize_t n = read(fd, *reply + read_bytes, *reply_size - read_bytes); ++ ++ if (n == 0) { ++ fprintf(stderr, "Unexpectedly reached EOF while reading payload."); ++ fprintf(stderr, "Read %" PRIu32 " bytes, expected %" PRIu32 " bytes.\n", ++ read_bytes, *reply_size); ++ free(*reply); ++ return -2; ++ } else if (n == -1) { ++ // TODO: Should we return and wait for another epoll event? ++ // This would require saving the partial read in some way. ++ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; ++ ++ free(*reply); ++ return -1; ++ } ++ ++ read_bytes += n; ++ } ++ ++ return 0; ++} ++ ++/** ++ * Internal function used to write a buffer to a file descriptor ++ * ++ * Returns number of bytes written if successful write ++ * Returns 0 if no bytes were written due to EAGAIN or EWOULDBLOCK ++ * Returns -1 on unknown error trying to write, errno will carry over from ++ * write() call ++ */ ++static ssize_t ++ipc_write_message(int fd, const void *buf, size_t count) ++{ ++ size_t written = 0; ++ ++ while (written < count) { ++ const ssize_t n = write(fd, (uint8_t *)buf + written, count - written); ++ ++ if (n == -1) { ++ if (errno == EAGAIN || errno == EWOULDBLOCK) ++ return written; ++ else if (errno == EINTR) ++ continue; ++ else ++ return n; ++ } ++ ++ written += n; ++ DEBUG("Wrote %zu/%zu to client at fd %d\n", written, count, fd); ++ } ++ ++ return written; ++} ++ ++/** ++ * Initialization for generic event message. This is used to allocate the yajl ++ * handle, set yajl options, and in the future any other initialization that ++ * should occur for event messages. ++ */ ++static void ++ipc_event_init_message(yajl_gen *gen) ++{ ++ *gen = yajl_gen_alloc(NULL); ++ yajl_gen_config(*gen, yajl_gen_beautify, 1); ++} ++ ++/** ++ * Prepares buffers of IPC subscribers of specified event using buffer from yajl ++ * handle. ++ */ ++static void ++ipc_event_prepare_send_message(yajl_gen gen, IPCEvent event) ++{ ++ const unsigned char *buffer; ++ size_t len = 0; ++ ++ yajl_gen_get_buf(gen, &buffer, &len); ++ len++; // For null char ++ ++ for (IPCClient *c = ipc_clients; c; c = c->next) { ++ if (c->subscriptions & event) { ++ DEBUG("Sending selected client change event to fd %d\n", c->fd); ++ ipc_prepare_send_message(c, IPC_TYPE_EVENT, len, (char *)buffer); ++ } ++ } ++ ++ // Not documented, but this frees temp_buffer ++ yajl_gen_free(gen); ++} ++ ++/** ++ * Initialization for generic reply message. This is used to allocate the yajl ++ * handle, set yajl options, and in the future any other initialization that ++ * should occur for reply messages. ++ */ ++static void ++ipc_reply_init_message(yajl_gen *gen) ++{ ++ *gen = yajl_gen_alloc(NULL); ++ yajl_gen_config(*gen, yajl_gen_beautify, 1); ++} ++ ++/** ++ * Prepares the IPC client's buffer with a message using the buffer of the yajl ++ * handle. ++ */ ++static void ++ipc_reply_prepare_send_message(yajl_gen gen, IPCClient *c, ++ IPCMessageType msg_type) ++{ ++ const unsigned char *buffer; ++ size_t len = 0; ++ ++ yajl_gen_get_buf(gen, &buffer, &len); ++ len++; // For null char ++ ++ ipc_prepare_send_message(c, msg_type, len, (const char *)buffer); ++ ++ // Not documented, but this frees temp_buffer ++ yajl_gen_free(gen); ++} ++ ++/** ++ * Find the IPCCommand with the specified name ++ * ++ * Returns 0 if a command with the specified name was found ++ * Returns -1 if a command with the specified name could not be found ++ */ ++static int ++ipc_get_ipc_command(const char *name, IPCCommand *ipc_command) ++{ ++ for (int i = 0; i < ipc_commands_len; i++) { ++ if (strcmp(ipc_commands[i].name, name) == 0) { ++ *ipc_command = ipc_commands[i]; ++ return 0; ++ } ++ } ++ ++ return -1; ++} ++ ++/** ++ * Parse a IPC_TYPE_RUN_COMMAND message from a client. This function extracts ++ * the arguments, argument count, argument types, and command name and returns ++ * the parsed information as an IPCParsedCommand. If this function returns ++ * successfully, the parsed_command must be freed using ++ * ipc_free_parsed_command_members. ++ * ++ * Returns 0 if the message was successfully parsed ++ * Returns -1 otherwise ++ */ ++static int ++ipc_parse_run_command(char *msg, IPCParsedCommand *parsed_command) ++{ ++ char error_buffer[1000]; ++ yajl_val parent = yajl_tree_parse(msg, error_buffer, 1000); ++ ++ if (parent == NULL) { ++ fputs("Failed to parse command from client\n", stderr); ++ fprintf(stderr, "%s\n", error_buffer); ++ fprintf(stderr, "Tried to parse: %s\n", msg); ++ return -1; ++ } ++ ++ // Format: ++ // { ++ // "command": "" ++ // "args": [ "arg1", "arg2", ... ] ++ // } ++ const char *command_path[] = {"command", 0}; ++ yajl_val command_val = yajl_tree_get(parent, command_path, yajl_t_string); ++ ++ if (command_val == NULL) { ++ fputs("No command key found in client message\n", stderr); ++ yajl_tree_free(parent); ++ return -1; ++ } ++ ++ const char *command_name = YAJL_GET_STRING(command_val); ++ size_t command_name_len = strlen(command_name); ++ parsed_command->name = (char *)malloc((command_name_len + 1) * sizeof(char)); ++ strcpy(parsed_command->name, command_name); ++ ++ DEBUG("Received command: %s\n", parsed_command->name); ++ ++ const char *args_path[] = {"args", 0}; ++ yajl_val args_val = yajl_tree_get(parent, args_path, yajl_t_array); ++ ++ if (args_val == NULL) { ++ fputs("No args key found in client message\n", stderr); ++ yajl_tree_free(parent); ++ return -1; ++ } ++ ++ unsigned int *argc = &parsed_command->argc; ++ Arg **args = &parsed_command->args; ++ ArgType **arg_types = &parsed_command->arg_types; ++ ++ *argc = args_val->u.array.len; ++ ++ // If no arguments are specified, make a dummy argument to pass to the ++ // function. This is just the way dwm's void(Arg*) functions are setup. ++ if (*argc == 0) { ++ *args = (Arg *)malloc(sizeof(Arg)); ++ *arg_types = (ArgType *)malloc(sizeof(ArgType)); ++ (*arg_types)[0] = ARG_TYPE_NONE; ++ (*args)[0].i = 0; ++ (*argc)++; ++ } else if (*argc > 0) { ++ *args = (Arg *)calloc(*argc, sizeof(Arg)); ++ *arg_types = (ArgType *)malloc(*argc * sizeof(ArgType)); ++ ++ for (int i = 0; i < *argc; i++) { ++ yajl_val arg_val = args_val->u.array.values[i]; ++ ++ if (YAJL_IS_NUMBER(arg_val)) { ++ if (YAJL_IS_INTEGER(arg_val)) { ++ // Any values below 0 must be a signed int ++ if (YAJL_GET_INTEGER(arg_val) < 0) { ++ (*args)[i].i = YAJL_GET_INTEGER(arg_val); ++ (*arg_types)[i] = ARG_TYPE_SINT; ++ DEBUG("i=%ld\n", (*args)[i].i); ++ // Any values above 0 should be an unsigned int ++ } else if (YAJL_GET_INTEGER(arg_val) >= 0) { ++ (*args)[i].ui = YAJL_GET_INTEGER(arg_val); ++ (*arg_types)[i] = ARG_TYPE_UINT; ++ DEBUG("ui=%ld\n", (*args)[i].i); ++ } ++ // If the number is not an integer, it must be a float ++ } else { ++ (*args)[i].f = (float)YAJL_GET_DOUBLE(arg_val); ++ (*arg_types)[i] = ARG_TYPE_FLOAT; ++ DEBUG("f=%f\n", (*args)[i].f); ++ // If argument is not a number, it must be a string ++ } ++ } else if (YAJL_IS_STRING(arg_val)) { ++ char *arg_s = YAJL_GET_STRING(arg_val); ++ size_t arg_s_size = (strlen(arg_s) + 1) * sizeof(char); ++ (*args)[i].v = (char *)malloc(arg_s_size); ++ (*arg_types)[i] = ARG_TYPE_STR; ++ strcpy((char *)(*args)[i].v, arg_s); ++ } ++ } ++ } ++ ++ yajl_tree_free(parent); ++ ++ return 0; ++} ++ ++/** ++ * Free the members of a IPCParsedCommand struct ++ */ ++static void ++ipc_free_parsed_command_members(IPCParsedCommand *command) ++{ ++ for (int i = 0; i < command->argc; i++) { ++ if (command->arg_types[i] == ARG_TYPE_STR) free((void *)command->args[i].v); ++ } ++ free(command->args); ++ free(command->arg_types); ++ free(command->name); ++} ++ ++/** ++ * Check if the given arguments are the correct length and type. Also do any ++ * casting to correct the types. ++ * ++ * Returns 0 if the arguments were the correct length and types ++ * Returns -1 if the argument count doesn't match ++ * Returns -2 if the argument types don't match ++ */ ++static int ++ipc_validate_run_command(IPCParsedCommand *parsed, const IPCCommand actual) ++{ ++ if (actual.argc != parsed->argc) return -1; ++ ++ for (int i = 0; i < parsed->argc; i++) { ++ ArgType ptype = parsed->arg_types[i]; ++ ArgType atype = actual.arg_types[i]; ++ ++ if (ptype != atype) { ++ if (ptype == ARG_TYPE_UINT && atype == ARG_TYPE_PTR) ++ // If this argument is supposed to be a void pointer, cast it ++ parsed->args[i].v = (void *)parsed->args[i].ui; ++ else if (ptype == ARG_TYPE_UINT && atype == ARG_TYPE_SINT) ++ // If this argument is supposed to be a signed int, cast it ++ parsed->args[i].i = parsed->args[i].ui; ++ else ++ return -2; ++ } ++ } ++ ++ return 0; ++} ++ ++/** ++ * Convert event name to their IPCEvent equivalent enum value ++ * ++ * Returns 0 if a valid event name was given ++ * Returns -1 otherwise ++ */ ++static int ++ipc_event_stoi(const char *subscription, IPCEvent *event) ++{ ++ if (strcmp(subscription, "tag_change_event") == 0) ++ *event = IPC_EVENT_TAG_CHANGE; ++ else if (strcmp(subscription, "client_focus_change_event") == 0) ++ *event = IPC_EVENT_CLIENT_FOCUS_CHANGE; ++ else if (strcmp(subscription, "layout_change_event") == 0) ++ *event = IPC_EVENT_LAYOUT_CHANGE; ++ else if (strcmp(subscription, "monitor_focus_change_event") == 0) ++ *event = IPC_EVENT_MONITOR_FOCUS_CHANGE; ++ else if (strcmp(subscription, "focused_title_change_event") == 0) ++ *event = IPC_EVENT_FOCUSED_TITLE_CHANGE; ++ else if (strcmp(subscription, "focused_state_change_event") == 0) ++ *event = IPC_EVENT_FOCUSED_STATE_CHANGE; ++ else ++ return -1; ++ return 0; ++} ++ ++/** ++ * Parse a IPC_TYPE_SUBSCRIBE message from a client. This function extracts the ++ * event name and the subscription action from the message. ++ * ++ * Returns 0 if message was successfully parsed ++ * Returns -1 otherwise ++ */ ++static int ++ipc_parse_subscribe(const char *msg, IPCSubscriptionAction *subscribe, ++ IPCEvent *event) ++{ ++ char error_buffer[100]; ++ yajl_val parent = yajl_tree_parse((char *)msg, error_buffer, 100); ++ ++ if (parent == NULL) { ++ fputs("Failed to parse command from client\n", stderr); ++ fprintf(stderr, "%s\n", error_buffer); ++ return -1; ++ } ++ ++ // Format: ++ // { ++ // "event": "" ++ // "action": "" ++ // } ++ const char *event_path[] = {"event", 0}; ++ yajl_val event_val = yajl_tree_get(parent, event_path, yajl_t_string); ++ ++ if (event_val == NULL) { ++ fputs("No 'event' key found in client message\n", stderr); ++ return -1; ++ } ++ ++ const char *event_str = YAJL_GET_STRING(event_val); ++ DEBUG("Received event: %s\n", event_str); ++ ++ if (ipc_event_stoi(event_str, event) < 0) return -1; ++ ++ const char *action_path[] = {"action", 0}; ++ yajl_val action_val = yajl_tree_get(parent, action_path, yajl_t_string); ++ ++ if (action_val == NULL) { ++ fputs("No 'action' key found in client message\n", stderr); ++ return -1; ++ } ++ ++ const char *action = YAJL_GET_STRING(action_val); ++ ++ if (strcmp(action, "subscribe") == 0) ++ *subscribe = IPC_ACTION_SUBSCRIBE; ++ else if (strcmp(action, "unsubscribe") == 0) ++ *subscribe = IPC_ACTION_UNSUBSCRIBE; ++ else { ++ fputs("Invalid action specified for subscription\n", stderr); ++ return -1; ++ } ++ ++ yajl_tree_free(parent); ++ ++ return 0; ++} ++ ++/** ++ * Parse an IPC_TYPE_GET_DWM_CLIENT message from a client. This function ++ * extracts the window id from the message. ++ * ++ * Returns 0 if message was successfully parsed ++ * Returns -1 otherwise ++ */ ++static int ++ipc_parse_get_dwm_client(const char *msg, Window *win) ++{ ++ char error_buffer[100]; ++ ++ yajl_val parent = yajl_tree_parse(msg, error_buffer, 100); ++ ++ if (parent == NULL) { ++ fputs("Failed to parse message from client\n", stderr); ++ fprintf(stderr, "%s\n", error_buffer); ++ return -1; ++ } ++ ++ // Format: ++ // { ++ // "client_window_id": ++ // } ++ const char *win_path[] = {"client_window_id", 0}; ++ yajl_val win_val = yajl_tree_get(parent, win_path, yajl_t_number); ++ ++ if (win_val == NULL) { ++ fputs("No client window id found in client message\n", stderr); ++ return -1; ++ } ++ ++ *win = YAJL_GET_INTEGER(win_val); ++ ++ yajl_tree_free(parent); ++ ++ return 0; ++} ++ ++/** ++ * Called when an IPC_TYPE_RUN_COMMAND message is received from a client. This ++ * function parses, executes the given command, and prepares a reply message to ++ * the client indicating success/failure. ++ * ++ * NOTE: There is currently no check for argument validity beyond the number of ++ * arguments given and types of arguments. There is also no way to check if the ++ * function succeeded based on dwm's void(const Arg*) function types. Pointer ++ * arguments can cause crashes if they are not validated in the function itself. ++ * ++ * Returns 0 if message was successfully parsed ++ * Returns -1 on failure parsing message ++ */ ++static int ++ipc_run_command(IPCClient *ipc_client, char *msg) ++{ ++ IPCParsedCommand parsed_command; ++ IPCCommand ipc_command; ++ ++ // Initialize struct ++ memset(&parsed_command, 0, sizeof(IPCParsedCommand)); ++ ++ if (ipc_parse_run_command(msg, &parsed_command) < 0) { ++ ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND, ++ "Failed to parse run command"); ++ return -1; ++ } ++ ++ if (ipc_get_ipc_command(parsed_command.name, &ipc_command) < 0) { ++ ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND, ++ "Command %s not found", parsed_command.name); ++ ipc_free_parsed_command_members(&parsed_command); ++ return -1; ++ } ++ ++ int res = ipc_validate_run_command(&parsed_command, ipc_command); ++ if (res < 0) { ++ if (res == -1) ++ ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND, ++ "%u arguments provided, %u expected", ++ parsed_command.argc, ipc_command.argc); ++ else if (res == -2) ++ ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND, ++ "Type mismatch"); ++ ipc_free_parsed_command_members(&parsed_command); ++ return -1; ++ } ++ ++ if (parsed_command.argc == 1) ++ ipc_command.func.single_param(parsed_command.args); ++ else if (parsed_command.argc > 1) ++ ipc_command.func.array_param(parsed_command.args, parsed_command.argc); ++ ++ DEBUG("Called function for command %s\n", parsed_command.name); ++ ++ ipc_free_parsed_command_members(&parsed_command); ++ ++ ipc_prepare_reply_success(ipc_client, IPC_TYPE_RUN_COMMAND); ++ return 0; ++} ++ ++/** ++ * Called when an IPC_TYPE_GET_MONITORS message is received from a client. It ++ * prepares a reply with the properties of all of the monitors in JSON. ++ */ ++static void ++ipc_get_monitors(IPCClient *c, Monitor *mons, Monitor *selmon) ++{ ++ yajl_gen gen; ++ ipc_reply_init_message(&gen); ++ dump_monitors(gen, mons, selmon); ++ ++ ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_MONITORS); ++} ++ ++/** ++ * Called when an IPC_TYPE_GET_TAGS message is received from a client. It ++ * prepares a reply with info about all the tags in JSON. ++ */ ++static void ++ipc_get_tags(IPCClient *c, const char *tags[], const int tags_len) ++{ ++ yajl_gen gen; ++ ipc_reply_init_message(&gen); ++ ++ dump_tags(gen, tags, tags_len); ++ ++ ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_TAGS); ++} ++ ++/** ++ * Called when an IPC_TYPE_GET_LAYOUTS message is received from a client. It ++ * prepares a reply with a JSON array of available layouts ++ */ ++static void ++ipc_get_layouts(IPCClient *c, const Layout layouts[], const int layouts_len) ++{ ++ yajl_gen gen; ++ ipc_reply_init_message(&gen); ++ ++ dump_layouts(gen, layouts, layouts_len); ++ ++ ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_LAYOUTS); ++} ++ ++/** ++ * Called when an IPC_TYPE_GET_DWM_CLIENT message is received from a client. It ++ * prepares a JSON reply with the properties of the client with the specified ++ * window XID. ++ * ++ * Returns 0 if the message was successfully parsed and if the client with the ++ * specified window XID was found ++ * Returns -1 if the message could not be parsed ++ */ ++static int ++ipc_get_dwm_client(IPCClient *ipc_client, const char *msg, const Monitor *mons) ++{ ++ Window win; ++ ++ if (ipc_parse_get_dwm_client(msg, &win) < 0) return -1; ++ ++ // Find client with specified window XID ++ for (const Monitor *m = mons; m; m = m->next) ++ for (Client *c = m->clients; c; c = c->next) ++ if (c->win == win) { ++ yajl_gen gen; ++ ipc_reply_init_message(&gen); ++ ++ dump_client(gen, c); ++ ++ ipc_reply_prepare_send_message(gen, ipc_client, ++ IPC_TYPE_GET_DWM_CLIENT); ++ ++ return 0; ++ } ++ ++ ipc_prepare_reply_failure(ipc_client, IPC_TYPE_GET_DWM_CLIENT, ++ "Client with window id %d not found", win); ++ return -1; ++} ++ ++/** ++ * Called when an IPC_TYPE_SUBSCRIBE message is received from a client. It ++ * subscribes/unsubscribes the client from the specified event and replies with ++ * the result. ++ * ++ * Returns 0 if the message was successfully parsed. ++ * Returns -1 if the message could not be parsed ++ */ ++static int ++ipc_subscribe(IPCClient *c, const char *msg) ++{ ++ IPCSubscriptionAction action = IPC_ACTION_SUBSCRIBE; ++ IPCEvent event = 0; ++ ++ if (ipc_parse_subscribe(msg, &action, &event)) { ++ ipc_prepare_reply_failure(c, IPC_TYPE_SUBSCRIBE, "Event does not exist"); ++ return -1; ++ } ++ ++ if (action == IPC_ACTION_SUBSCRIBE) { ++ DEBUG("Subscribing client on fd %d to %d\n", c->fd, event); ++ c->subscriptions |= event; ++ } else if (action == IPC_ACTION_UNSUBSCRIBE) { ++ DEBUG("Unsubscribing client on fd %d to %d\n", c->fd, event); ++ c->subscriptions ^= event; ++ } else { ++ ipc_prepare_reply_failure(c, IPC_TYPE_SUBSCRIBE, ++ "Invalid subscription action"); ++ return -1; ++ } ++ ++ ipc_prepare_reply_success(c, IPC_TYPE_SUBSCRIBE); ++ return 0; ++} ++ ++int ++ipc_init(const char *socket_path, const int p_epoll_fd, IPCCommand commands[], ++ const int commands_len) ++{ ++ // Initialize struct to 0 ++ memset(&sock_epoll_event, 0, sizeof(sock_epoll_event)); ++ ++ int socket_fd = ipc_create_socket(socket_path); ++ if (socket_fd < 0) return -1; ++ ++ ipc_commands = commands; ++ ipc_commands_len = commands_len; ++ ++ epoll_fd = p_epoll_fd; ++ ++ // Wake up to incoming connection requests ++ sock_epoll_event.data.fd = socket_fd; ++ sock_epoll_event.events = EPOLLIN; ++ if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &sock_epoll_event)) { ++ fputs("Failed to add sock file descriptor to epoll", stderr); ++ return -1; ++ } ++ ++ return socket_fd; ++} ++ ++void ++ipc_cleanup() ++{ ++ IPCClient *c = ipc_clients; ++ // Free clients and their buffers ++ while (c) { ++ ipc_drop_client(c); ++ c = ipc_clients; ++ } ++ ++ // Stop waking up for socket events ++ epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock_fd, &sock_epoll_event); ++ ++ // Uninitialize all static variables ++ epoll_fd = -1; ++ sock_fd = -1; ++ ipc_commands = NULL; ++ ipc_commands_len = 0; ++ memset(&sock_epoll_event, 0, sizeof(struct epoll_event)); ++ memset(&sockaddr, 0, sizeof(struct sockaddr_un)); ++ ++ // Delete socket ++ unlink(sockaddr.sun_path); ++ ++ shutdown(sock_fd, SHUT_RDWR); ++ close(sock_fd); ++} ++ ++int ++ipc_get_sock_fd() ++{ ++ return sock_fd; ++} ++ ++IPCClient * ++ipc_get_client(int fd) ++{ ++ return ipc_list_get_client(ipc_clients, fd); ++} ++ ++int ++ipc_is_client_registered(int fd) ++{ ++ return (ipc_get_client(fd) != NULL); ++} ++ ++int ++ipc_accept_client() ++{ ++ int fd = -1; ++ ++ struct sockaddr_un client_addr; ++ socklen_t len = 0; ++ ++ // For portability clear the addr structure, since some implementations ++ // have nonstandard fields in the structure ++ memset(&client_addr, 0, sizeof(struct sockaddr_un)); ++ ++ fd = accept(sock_fd, (struct sockaddr *)&client_addr, &len); ++ if (fd < 0 && errno != EINTR) { ++ fputs("Failed to accept IPC connection from client", stderr); ++ return -1; ++ } ++ ++ if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { ++ shutdown(fd, SHUT_RDWR); ++ close(fd); ++ fputs("Failed to set flags on new client fd", stderr); ++ } ++ ++ IPCClient *nc = ipc_client_new(fd); ++ if (nc == NULL) return -1; ++ ++ // Wake up to messages from this client ++ nc->event.data.fd = fd; ++ nc->event.events = EPOLLIN | EPOLLHUP; ++ epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &nc->event); ++ ++ ipc_list_add_client(&ipc_clients, nc); ++ ++ DEBUG("%s%d\n", "New client at fd: ", fd); ++ ++ return fd; ++} ++ ++int ++ipc_drop_client(IPCClient *c) ++{ ++ int fd = c->fd; ++ shutdown(fd, SHUT_RDWR); ++ int res = close(fd); ++ ++ if (res == 0) { ++ struct epoll_event ev; ++ ++ // Stop waking up to messages from this client ++ epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev); ++ ipc_list_remove_client(&ipc_clients, c); ++ ++ free(c->buffer); ++ free(c); ++ ++ DEBUG("Successfully removed client on fd %d\n", fd); ++ } else if (res < 0 && res != EINTR) { ++ fprintf(stderr, "Failed to close fd %d\n", fd); ++ } ++ ++ return res; ++} ++ ++int ++ipc_read_client(IPCClient *c, IPCMessageType *msg_type, uint32_t *msg_size, ++ char **msg) ++{ ++ int fd = c->fd; ++ int ret = ++ ipc_recv_message(fd, (uint8_t *)msg_type, msg_size, (uint8_t **)msg); ++ ++ if (ret < 0) { ++ // This will happen if these errors occur while reading header ++ if (ret == -1 && ++ (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) ++ return -2; ++ ++ fprintf(stderr, "Error reading message: dropping client at fd %d\n", fd); ++ ipc_drop_client(c); ++ ++ return -1; ++ } ++ ++ // Make sure receive message is null terminated to avoid parsing issues ++ if (*msg_size > 0) { ++ size_t len = *msg_size; ++ nullterminate(msg, &len); ++ *msg_size = len; ++ } ++ ++ DEBUG("[fd %d] ", fd); ++ if (*msg_size > 0) ++ DEBUG("Received message: '%.*s' ", *msg_size, *msg); ++ else ++ DEBUG("Received empty message "); ++ DEBUG("Message type: %" PRIu8 " ", (uint8_t)*msg_type); ++ DEBUG("Message size: %" PRIu32 "\n", *msg_size); ++ ++ return 0; ++} ++ ++ssize_t ++ipc_write_client(IPCClient *c) ++{ ++ const ssize_t n = ipc_write_message(c->fd, c->buffer, c->buffer_size); ++ ++ if (n < 0) return n; ++ ++ // TODO: Deal with client timeouts ++ ++ if (n == c->buffer_size) { ++ c->buffer_size = 0; ++ free(c->buffer); ++ // No dangling pointers! ++ c->buffer = NULL; ++ // Stop waking up when client is ready to receive messages ++ if (c->event.events & EPOLLOUT) { ++ c->event.events -= EPOLLOUT; ++ epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event); ++ } ++ return n; ++ } ++ ++ // Shift unwritten buffer to beginning of buffer and reallocate ++ c->buffer_size -= n; ++ memmove(c->buffer, c->buffer + n, c->buffer_size); ++ c->buffer = (char *)realloc(c->buffer, c->buffer_size); ++ ++ return n; ++} ++ ++void ++ipc_prepare_send_message(IPCClient *c, const IPCMessageType msg_type, ++ const uint32_t msg_size, const char *msg) ++{ ++ dwm_ipc_header_t header = { ++ .magic = IPC_MAGIC_ARR, .type = msg_type, .size = msg_size}; ++ ++ uint32_t header_size = sizeof(dwm_ipc_header_t); ++ uint32_t packet_size = header_size + msg_size; ++ ++ if (c->buffer == NULL) ++ c->buffer = (char *)malloc(c->buffer_size + packet_size); ++ else ++ c->buffer = (char *)realloc(c->buffer, c->buffer_size + packet_size); ++ ++ // Copy header to end of client buffer ++ memcpy(c->buffer + c->buffer_size, &header, header_size); ++ c->buffer_size += header_size; ++ ++ // Copy message to end of client buffer ++ memcpy(c->buffer + c->buffer_size, msg, msg_size); ++ c->buffer_size += msg_size; ++ ++ // Wake up when client is ready to receive messages ++ c->event.events |= EPOLLOUT; ++ epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event); ++} ++ ++void ++ipc_prepare_reply_failure(IPCClient *c, IPCMessageType msg_type, ++ const char *format, ...) ++{ ++ yajl_gen gen; ++ va_list args; ++ ++ // Get output size ++ va_start(args, format); ++ size_t len = vsnprintf(NULL, 0, format, args); ++ va_end(args); ++ char *buffer = (char *)malloc((len + 1) * sizeof(char)); ++ ++ ipc_reply_init_message(&gen); ++ ++ va_start(args, format); ++ vsnprintf(buffer, len + 1, format, args); ++ va_end(args); ++ dump_error_message(gen, buffer); ++ ++ ipc_reply_prepare_send_message(gen, c, msg_type); ++ fprintf(stderr, "[fd %d] Error: %s\n", c->fd, buffer); ++ ++ free(buffer); ++} ++ ++void ++ipc_prepare_reply_success(IPCClient *c, IPCMessageType msg_type) ++{ ++ const char *success_msg = "{\"result\":\"success\"}"; ++ const size_t msg_len = strlen(success_msg) + 1; // +1 for null char ++ ++ ipc_prepare_send_message(c, msg_type, msg_len, success_msg); ++} ++ ++void ++ipc_tag_change_event(int mon_num, TagState old_state, TagState new_state) ++{ ++ yajl_gen gen; ++ ipc_event_init_message(&gen); ++ dump_tag_event(gen, mon_num, old_state, new_state); ++ ipc_event_prepare_send_message(gen, IPC_EVENT_TAG_CHANGE); ++} ++ ++void ++ipc_client_focus_change_event(int mon_num, Client *old_client, ++ Client *new_client) ++{ ++ yajl_gen gen; ++ ipc_event_init_message(&gen); ++ dump_client_focus_change_event(gen, old_client, new_client, mon_num); ++ ipc_event_prepare_send_message(gen, IPC_EVENT_CLIENT_FOCUS_CHANGE); ++} ++ ++void ++ipc_layout_change_event(const int mon_num, const char *old_symbol, ++ const Layout *old_layout, const char *new_symbol, ++ const Layout *new_layout) ++{ ++ yajl_gen gen; ++ ipc_event_init_message(&gen); ++ dump_layout_change_event(gen, mon_num, old_symbol, old_layout, new_symbol, ++ new_layout); ++ ipc_event_prepare_send_message(gen, IPC_EVENT_LAYOUT_CHANGE); ++} ++ ++void ++ipc_monitor_focus_change_event(const int last_mon_num, const int new_mon_num) ++{ ++ yajl_gen gen; ++ ipc_event_init_message(&gen); ++ dump_monitor_focus_change_event(gen, last_mon_num, new_mon_num); ++ ipc_event_prepare_send_message(gen, IPC_EVENT_MONITOR_FOCUS_CHANGE); ++} ++ ++void ++ipc_focused_title_change_event(const int mon_num, const Window client_id, ++ const char *old_name, const char *new_name) ++{ ++ yajl_gen gen; ++ ipc_event_init_message(&gen); ++ dump_focused_title_change_event(gen, mon_num, client_id, old_name, new_name); ++ ipc_event_prepare_send_message(gen, IPC_EVENT_FOCUSED_TITLE_CHANGE); ++} ++ ++void ++ipc_focused_state_change_event(const int mon_num, const Window client_id, ++ const ClientState *old_state, ++ const ClientState *new_state) ++{ ++ yajl_gen gen; ++ ipc_event_init_message(&gen); ++ dump_focused_state_change_event(gen, mon_num, client_id, old_state, ++ new_state); ++ ipc_event_prepare_send_message(gen, IPC_EVENT_FOCUSED_STATE_CHANGE); ++} ++ ++void ++ipc_send_events(Monitor *mons, Monitor **lastselmon, Monitor *selmon) ++{ ++ for (Monitor *m = mons; m; m = m->next) { ++ unsigned int urg = 0, occ = 0, tagset = 0; ++ ++ for (Client *c = m->clients; c; c = c->next) { ++ occ |= c->tags; ++ ++ if (c->isurgent) urg |= c->tags; ++ } ++ tagset = m->tagset[m->seltags]; ++ ++ TagState new_state = {.selected = tagset, .occupied = occ, .urgent = urg}; ++ ++ if (memcmp(&m->tagstate, &new_state, sizeof(TagState)) != 0) { ++ ipc_tag_change_event(m->num, m->tagstate, new_state); ++ m->tagstate = new_state; ++ } ++ ++ if (m->lastsel != m->sel) { ++ ipc_client_focus_change_event(m->num, m->lastsel, m->sel); ++ m->lastsel = m->sel; ++ } ++ ++ if (strcmp(m->ltsymbol, m->lastltsymbol) != 0 || ++ m->lastlt != m->lt[m->sellt]) { ++ ipc_layout_change_event(m->num, m->lastltsymbol, m->lastlt, m->ltsymbol, ++ m->lt[m->sellt]); ++ strcpy(m->lastltsymbol, m->ltsymbol); ++ m->lastlt = m->lt[m->sellt]; ++ } ++ ++ if (*lastselmon != selmon) { ++ if (*lastselmon != NULL) ++ ipc_monitor_focus_change_event((*lastselmon)->num, selmon->num); ++ *lastselmon = selmon; ++ } ++ ++ Client *sel = m->sel; ++ if (!sel) continue; ++ ClientState *o = &m->sel->prevstate; ++ ClientState n = {.oldstate = sel->oldstate, ++ .isfixed = sel->isfixed, ++ .isfloating = sel->isfloating, ++ .isfullscreen = sel->isfullscreen, ++ .isurgent = sel->isurgent, ++ .neverfocus = sel->neverfocus}; ++ if (memcmp(o, &n, sizeof(ClientState)) != 0) { ++ ipc_focused_state_change_event(m->num, m->sel->win, o, &n); ++ *o = n; ++ } ++ } ++} ++ ++int ++ipc_handle_client_epoll_event(struct epoll_event *ev, Monitor *mons, ++ Monitor **lastselmon, Monitor *selmon, ++ const char *tags[], const int tags_len, ++ const Layout *layouts, const int layouts_len) ++{ ++ int fd = ev->data.fd; ++ IPCClient *c = ipc_get_client(fd); ++ ++ if (ev->events & EPOLLHUP) { ++ DEBUG("EPOLLHUP received from client at fd %d\n", fd); ++ ipc_drop_client(c); ++ } else if (ev->events & EPOLLOUT) { ++ DEBUG("Sending message to client at fd %d...\n", fd); ++ if (c->buffer_size) ipc_write_client(c); ++ } else if (ev->events & EPOLLIN) { ++ IPCMessageType msg_type = 0; ++ uint32_t msg_size = 0; ++ char *msg = NULL; ++ ++ DEBUG("Received message from fd %d\n", fd); ++ if (ipc_read_client(c, &msg_type, &msg_size, &msg) < 0) return -1; ++ ++ if (msg_type == IPC_TYPE_GET_MONITORS) ++ ipc_get_monitors(c, mons, selmon); ++ else if (msg_type == IPC_TYPE_GET_TAGS) ++ ipc_get_tags(c, tags, tags_len); ++ else if (msg_type == IPC_TYPE_GET_LAYOUTS) ++ ipc_get_layouts(c, layouts, layouts_len); ++ else if (msg_type == IPC_TYPE_RUN_COMMAND) { ++ if (ipc_run_command(c, msg) < 0) return -1; ++ ipc_send_events(mons, lastselmon, selmon); ++ } else if (msg_type == IPC_TYPE_GET_DWM_CLIENT) { ++ if (ipc_get_dwm_client(c, msg, mons) < 0) return -1; ++ } else if (msg_type == IPC_TYPE_SUBSCRIBE) { ++ if (ipc_subscribe(c, msg) < 0) return -1; ++ } else { ++ fprintf(stderr, "Invalid message type received from fd %d", fd); ++ ipc_prepare_reply_failure(c, msg_type, "Invalid message type: %d", ++ msg_type); ++ } ++ free(msg); ++ } else { ++ fprintf(stderr, "Epoll event returned %d from fd %d\n", ev->events, fd); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++int ++ipc_handle_socket_epoll_event(struct epoll_event *ev) ++{ ++ if (!(ev->events & EPOLLIN)) return -1; ++ ++ // EPOLLIN means incoming client connection request ++ fputs("Received EPOLLIN event on socket\n", stderr); ++ int new_fd = ipc_accept_client(); ++ ++ return new_fd; ++} +diff --git a/ipc.h b/ipc.h +new file mode 100644 +index 0000000..e3b5bba +--- /dev/null ++++ b/ipc.h +@@ -0,0 +1,320 @@ ++#ifndef IPC_H_ ++#define IPC_H_ ++ ++#include ++#include ++#include ++ ++#include "IPCClient.h" ++ ++// clang-format off ++#define IPC_MAGIC "DWM-IPC" ++#define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C'} ++#define IPC_MAGIC_LEN 7 // Not including null char ++ ++#define IPCCOMMAND(FUNC, ARGC, TYPES) \ ++ { #FUNC, {FUNC }, ARGC, (ArgType[ARGC])TYPES } ++// clang-format on ++ ++typedef enum IPCMessageType { ++ IPC_TYPE_RUN_COMMAND = 0, ++ IPC_TYPE_GET_MONITORS = 1, ++ IPC_TYPE_GET_TAGS = 2, ++ IPC_TYPE_GET_LAYOUTS = 3, ++ IPC_TYPE_GET_DWM_CLIENT = 4, ++ IPC_TYPE_SUBSCRIBE = 5, ++ IPC_TYPE_EVENT = 6 ++} IPCMessageType; ++ ++typedef enum IPCEvent { ++ IPC_EVENT_TAG_CHANGE = 1 << 0, ++ IPC_EVENT_CLIENT_FOCUS_CHANGE = 1 << 1, ++ IPC_EVENT_LAYOUT_CHANGE = 1 << 2, ++ IPC_EVENT_MONITOR_FOCUS_CHANGE = 1 << 3, ++ IPC_EVENT_FOCUSED_TITLE_CHANGE = 1 << 4, ++ IPC_EVENT_FOCUSED_STATE_CHANGE = 1 << 5 ++} IPCEvent; ++ ++typedef enum IPCSubscriptionAction { ++ IPC_ACTION_UNSUBSCRIBE = 0, ++ IPC_ACTION_SUBSCRIBE = 1 ++} IPCSubscriptionAction; ++ ++/** ++ * Every IPC packet starts with this structure ++ */ ++typedef struct dwm_ipc_header { ++ uint8_t magic[IPC_MAGIC_LEN]; ++ uint32_t size; ++ uint8_t type; ++} __attribute((packed)) dwm_ipc_header_t; ++ ++typedef enum ArgType { ++ ARG_TYPE_NONE = 0, ++ ARG_TYPE_UINT = 1, ++ ARG_TYPE_SINT = 2, ++ ARG_TYPE_FLOAT = 3, ++ ARG_TYPE_PTR = 4, ++ ARG_TYPE_STR = 5 ++} ArgType; ++ ++/** ++ * An IPCCommand function can have either of these function signatures ++ */ ++typedef union ArgFunction { ++ void (*single_param)(const Arg *); ++ void (*array_param)(const Arg *, int); ++} ArgFunction; ++ ++typedef struct IPCCommand { ++ char *name; ++ ArgFunction func; ++ unsigned int argc; ++ ArgType *arg_types; ++} IPCCommand; ++ ++typedef struct IPCParsedCommand { ++ char *name; ++ Arg *args; ++ ArgType *arg_types; ++ unsigned int argc; ++} IPCParsedCommand; ++ ++/** ++ * Initialize the IPC socket and the IPC module ++ * ++ * @param socket_path Path to create the socket at ++ * @param epoll_fd File descriptor for epoll ++ * @param commands Address of IPCCommands array defined in config.h ++ * @param commands_len Length of commands[] array ++ * ++ * @return int The file descriptor of the socket if it was successfully created, ++ * -1 otherwise ++ */ ++int ipc_init(const char *socket_path, const int p_epoll_fd, ++ IPCCommand commands[], const int commands_len); ++ ++/** ++ * Uninitialize the socket and module. Free allocated memory and restore static ++ * variables to their state before ipc_init ++ */ ++void ipc_cleanup(); ++ ++/** ++ * Get the file descriptor of the IPC socket ++ * ++ * @return int File descriptor of IPC socket, -1 if socket not created. ++ */ ++int ipc_get_sock_fd(); ++ ++/** ++ * Get address to IPCClient with specified file descriptor ++ * ++ * @param fd File descriptor of IPC Client ++ * ++ * @return Address to IPCClient with specified file descriptor, -1 otherwise ++ */ ++IPCClient *ipc_get_client(int fd); ++ ++/** ++ * Check if an IPC client exists with the specified file descriptor ++ * ++ * @param fd File descriptor ++ * ++ * @return int 1 if client exists, 0 otherwise ++ */ ++int ipc_is_client_registered(int fd); ++ ++/** ++ * Disconnect an IPCClient from the socket and remove the client from the list ++ * of known connected clients ++ * ++ * @param c Address of IPCClient ++ * ++ * @return 0 if the client's file descriptor was closed successfully, the ++ * result of executing close() on the file descriptor otherwise. ++ */ ++int ipc_drop_client(IPCClient *c); ++ ++/** ++ * Accept an IPC Client requesting to connect to the socket and add it to the ++ * list of clients ++ * ++ * @return File descriptor of new client, -1 on error ++ */ ++int ipc_accept_client(); ++ ++/** ++ * Read an incoming message from an accepted IPC client ++ * ++ * @param c Address of IPCClient ++ * @param msg_type Address to IPCMessageType variable which will be assigned ++ * the message type of the received message ++ * @param msg_size Address to uint32_t variable which will be assigned the size ++ * of the received message ++ * @param msg Address to char* variable which will be assigned the address of ++ * the received message. This must be freed using free(). ++ * ++ * @return 0 on success, -1 on error reading message, -2 if reading the message ++ * resulted in EAGAIN, EINTR, or EWOULDBLOCK. ++ */ ++int ipc_read_client(IPCClient *c, IPCMessageType *msg_type, uint32_t *msg_size, ++ char **msg); ++ ++/** ++ * Write any pending buffer of the client to the client's socket ++ * ++ * @param c Client whose buffer to write ++ * ++ * @return Number of bytes written >= 0, -1 otherwise. errno will still be set ++ * from the write operation. ++ */ ++ssize_t ipc_write_client(IPCClient *c); ++ ++/** ++ * Prepare a message in the specified client's buffer. ++ * ++ * @param c Client to prepare message for ++ * @param msg_type Type of message to prepare ++ * @param msg_size Size of the message in bytes. Should not exceed ++ * MAX_MESSAGE_SIZE ++ * @param msg Message to prepare (not including header). This pointer can be ++ * freed after the function invocation. ++ */ ++void ipc_prepare_send_message(IPCClient *c, const IPCMessageType msg_type, ++ const uint32_t msg_size, const char *msg); ++ ++/** ++ * Prepare an error message in the specified client's buffer ++ * ++ * @param c Client to prepare message for ++ * @param msg_type Type of message ++ * @param format Format string following vsprintf ++ * @param ... Arguments for format string ++ */ ++void ipc_prepare_reply_failure(IPCClient *c, IPCMessageType msg_type, ++ const char *format, ...); ++ ++/** ++ * Prepare a success message in the specified client's buffer ++ * ++ * @param c Client to prepare message for ++ * @param msg_type Type of message ++ */ ++void ipc_prepare_reply_success(IPCClient *c, IPCMessageType msg_type); ++ ++/** ++ * Send a tag_change_event to all subscribers. Should be called only when there ++ * has been a tag state change. ++ * ++ * @param mon_num The index of the monitor (Monitor.num property) ++ * @param old_state The old tag state ++ * @param new_state The new (now current) tag state ++ */ ++void ipc_tag_change_event(const int mon_num, TagState old_state, ++ TagState new_state); ++ ++/** ++ * Send a client_focus_change_event to all subscribers. Should be called only ++ * when the client focus changes. ++ * ++ * @param mon_num The index of the monitor (Monitor.num property) ++ * @param old_client The old DWM client selection (Monitor.oldsel) ++ * @param new_client The new (now current) DWM client selection ++ */ ++void ipc_client_focus_change_event(const int mon_num, Client *old_client, ++ Client *new_client); ++ ++/** ++ * Send a layout_change_event to all subscribers. Should be called only ++ * when there has been a layout change. ++ * ++ * @param mon_num The index of the monitor (Monitor.num property) ++ * @param old_symbol The old layout symbol ++ * @param old_layout Address to the old Layout ++ * @param new_symbol The new (now current) layout symbol ++ * @param new_layout Address to the new Layout ++ */ ++void ipc_layout_change_event(const int mon_num, const char *old_symbol, ++ const Layout *old_layout, const char *new_symbol, ++ const Layout *new_layout); ++ ++/** ++ * Send a monitor_focus_change_event to all subscribers. Should be called only ++ * when the monitor focus changes. ++ * ++ * @param last_mon_num The index of the previously selected monitor ++ * @param new_mon_num The index of the newly selected monitor ++ */ ++void ipc_monitor_focus_change_event(const int last_mon_num, ++ const int new_mon_num); ++ ++/** ++ * Send a focused_title_change_event to all subscribers. Should only be called ++ * if a selected client has a title change. ++ * ++ * @param mon_num Index of the client's monitor ++ * @param client_id Window XID of client ++ * @param old_name Old name of the client window ++ * @param new_name New name of the client window ++ */ ++void ipc_focused_title_change_event(const int mon_num, const Window client_id, ++ const char *old_name, const char *new_name); ++ ++/** ++ * Send a focused_state_change_event to all subscribers. Should only be called ++ * if a selected client has a state change. ++ * ++ * @param mon_num Index of the client's monitor ++ * @param client_id Window XID of client ++ * @param old_state Old state of the client ++ * @param new_state New state of the client ++ */ ++void ipc_focused_state_change_event(const int mon_num, const Window client_id, ++ const ClientState *old_state, ++ const ClientState *new_state); ++/** ++ * Check to see if an event has occured and call the *_change_event functions ++ * accordingly ++ * ++ * @param mons Address of Monitor pointing to start of linked list ++ * @param lastselmon Address of pointer to previously selected monitor ++ * @param selmon Address of selected Monitor ++ */ ++void ipc_send_events(Monitor *mons, Monitor **lastselmon, Monitor *selmon); ++ ++/** ++ * Handle an epoll event caused by a registered IPC client. Read, process, and ++ * handle any received messages from clients. Write pending buffer to client if ++ * the client is ready to receive messages. Drop clients that have sent an ++ * EPOLLHUP. ++ * ++ * @param ev Associated epoll event returned by epoll_wait ++ * @param mons Address of Monitor pointing to start of linked list ++ * @param selmon Address of selected Monitor ++ * @param lastselmon Address of pointer to previously selected monitor ++ * @param tags Array of tag names ++ * @param tags_len Length of tags array ++ * @param layouts Array of available layouts ++ * @param layouts_len Length of layouts array ++ * ++ * @return 0 if event was successfully handled, -1 on any error receiving ++ * or handling incoming messages or unhandled epoll event. ++ */ ++int ipc_handle_client_epoll_event(struct epoll_event *ev, Monitor *mons, ++ Monitor **lastselmon, Monitor *selmon, ++ const char *tags[], const int tags_len, ++ const Layout *layouts, const int layouts_len); ++ ++/** ++ * Handle an epoll event caused by the IPC socket. This function only handles an ++ * EPOLLIN event indicating a new client requesting to connect to the socket. ++ * ++ * @param ev Associated epoll event returned by epoll_wait ++ * ++ * @return 0, if the event was successfully handled, -1 if not an EPOLLIN event ++ * or if a new IPC client connection request could not be accepted. ++ */ ++int ipc_handle_socket_epoll_event(struct epoll_event *ev); ++ ++#endif /* IPC_H_ */ +diff --git a/util.c b/util.c +index fe044fc..dca4794 100644 +--- a/util.c ++++ b/util.c +@@ -3,6 +3,8 @@ + #include + #include + #include ++#include ++#include + + #include "util.h" + +@@ -33,3 +35,136 @@ die(const char *fmt, ...) { + + exit(1); + } ++ ++int ++normalizepath(const char *path, char **normal) ++{ ++ size_t len = strlen(path); ++ *normal = (char *)malloc((len + 1) * sizeof(char)); ++ const char *walk = path; ++ const char *match; ++ size_t newlen = 0; ++ ++ while ((match = strchr(walk, '/'))) { ++ // Copy everything between match and walk ++ strncpy(*normal + newlen, walk, match - walk); ++ newlen += match - walk; ++ walk += match - walk; ++ ++ // Skip all repeating slashes ++ while (*walk == '/') ++ walk++; ++ ++ // If not last character in path ++ if (walk != path + len) ++ (*normal)[newlen++] = '/'; ++ } ++ ++ (*normal)[newlen++] = '\0'; ++ ++ // Copy remaining path ++ strcat(*normal, walk); ++ newlen += strlen(walk); ++ ++ *normal = (char *)realloc(*normal, newlen * sizeof(char)); ++ ++ return 0; ++} ++ ++int ++parentdir(const char *path, char **parent) ++{ ++ char *normal; ++ char *walk; ++ ++ normalizepath(path, &normal); ++ ++ // Pointer to last '/' ++ if (!(walk = strrchr(normal, '/'))) { ++ free(normal); ++ return -1; ++ } ++ ++ // Get path up to last '/' ++ size_t len = walk - normal; ++ *parent = (char *)malloc((len + 1) * sizeof(char)); ++ ++ // Copy path up to last '/' ++ strncpy(*parent, normal, len); ++ // Add null char ++ (*parent)[len] = '\0'; ++ ++ free(normal); ++ ++ return 0; ++} ++ ++int ++mkdirp(const char *path) ++{ ++ char *normal; ++ char *walk; ++ size_t normallen; ++ ++ normalizepath(path, &normal); ++ normallen = strlen(normal); ++ walk = normal; ++ ++ while (walk < normal + normallen + 1) { ++ // Get length from walk to next / ++ size_t n = strcspn(walk, "/"); ++ ++ // Skip path / ++ if (n == 0) { ++ walk++; ++ continue; ++ } ++ ++ // Length of current path segment ++ size_t curpathlen = walk - normal + n; ++ char curpath[curpathlen + 1]; ++ struct stat s; ++ ++ // Copy path segment to stat ++ strncpy(curpath, normal, curpathlen); ++ strcpy(curpath + curpathlen, ""); ++ int res = stat(curpath, &s); ++ ++ if (res < 0) { ++ if (errno == ENOENT) { ++ DEBUG("Making directory %s\n", curpath); ++ if (mkdir(curpath, 0700) < 0) { ++ fprintf(stderr, "Failed to make directory %s\n", curpath); ++ perror(""); ++ free(normal); ++ return -1; ++ } ++ } else { ++ fprintf(stderr, "Error statting directory %s\n", curpath); ++ perror(""); ++ free(normal); ++ return -1; ++ } ++ } ++ ++ // Continue to next path segment ++ walk += n; ++ } ++ ++ free(normal); ++ ++ return 0; ++} ++ ++int ++nullterminate(char **str, size_t *len) ++{ ++ if ((*str)[*len - 1] == '\0') ++ return 0; ++ ++ (*len)++; ++ *str = (char*)realloc(*str, *len * sizeof(char)); ++ (*str)[*len - 1] = '\0'; ++ ++ return 0; ++} +diff --git a/util.h b/util.h +index f633b51..73a238e 100644 +--- a/util.h ++++ b/util.h +@@ -4,5 +4,15 @@ + #define MIN(A, B) ((A) < (B) ? (A) : (B)) + #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + ++#ifdef _DEBUG ++#define DEBUG(...) fprintf(stderr, __VA_ARGS__) ++#else ++#define DEBUG(...) ++#endif ++ + void die(const char *fmt, ...); + void *ecalloc(size_t nmemb, size_t size); ++int normalizepath(const char *path, char **normal); ++int mkdirp(const char *path); ++int parentdir(const char *path, char **parent); ++int nullterminate(char **str, size_t *len); +diff --git a/yajl_dumps.c b/yajl_dumps.c +new file mode 100644 +index 0000000..8bf9688 +--- /dev/null ++++ b/yajl_dumps.c +@@ -0,0 +1,351 @@ ++#include "yajl_dumps.h" ++ ++#include ++ ++int ++dump_tag(yajl_gen gen, const char *name, const int tag_mask) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("bit_mask"); YINT(tag_mask); ++ YSTR("name"); YSTR(name); ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_tags(yajl_gen gen, const char *tags[], int tags_len) ++{ ++ // clang-format off ++ YARR( ++ for (int i = 0; i < tags_len; i++) ++ dump_tag(gen, tags[i], 1 << i); ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_client(yajl_gen gen, Client *c) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("name"); YSTR(c->name); ++ YSTR("tags"); YINT(c->tags); ++ YSTR("window_id"); YINT(c->win); ++ YSTR("monitor_number"); YINT(c->mon->num); ++ ++ YSTR("geometry"); YMAP( ++ YSTR("current"); YMAP ( ++ YSTR("x"); YINT(c->x); ++ YSTR("y"); YINT(c->y); ++ YSTR("width"); YINT(c->w); ++ YSTR("height"); YINT(c->h); ++ ) ++ YSTR("old"); YMAP( ++ YSTR("x"); YINT(c->oldx); ++ YSTR("y"); YINT(c->oldy); ++ YSTR("width"); YINT(c->oldw); ++ YSTR("height"); YINT(c->oldh); ++ ) ++ ) ++ ++ YSTR("size_hints"); YMAP( ++ YSTR("base"); YMAP( ++ YSTR("width"); YINT(c->basew); ++ YSTR("height"); YINT(c->baseh); ++ ) ++ YSTR("step"); YMAP( ++ YSTR("width"); YINT(c->incw); ++ YSTR("height"); YINT(c->inch); ++ ) ++ YSTR("max"); YMAP( ++ YSTR("width"); YINT(c->maxw); ++ YSTR("height"); YINT(c->maxh); ++ ) ++ YSTR("min"); YMAP( ++ YSTR("width"); YINT(c->minw); ++ YSTR("height"); YINT(c->minh); ++ ) ++ YSTR("aspect_ratio"); YMAP( ++ YSTR("min"); YDOUBLE(c->mina); ++ YSTR("max"); YDOUBLE(c->maxa); ++ ) ++ ) ++ ++ YSTR("border_width"); YMAP( ++ YSTR("current"); YINT(c->bw); ++ YSTR("old"); YINT(c->oldbw); ++ ) ++ ++ YSTR("states"); YMAP( ++ YSTR("is_fixed"); YBOOL(c->isfixed); ++ YSTR("is_floating"); YBOOL(c->isfloating); ++ YSTR("is_urgent"); YBOOL(c->isurgent); ++ YSTR("never_focus"); YBOOL(c->neverfocus); ++ YSTR("old_state"); YBOOL(c->oldstate); ++ YSTR("is_fullscreen"); YBOOL(c->isfullscreen); ++ ) ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_monitor(yajl_gen gen, Monitor *mon, int is_selected) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("master_factor"); YDOUBLE(mon->mfact); ++ YSTR("num_master"); YINT(mon->nmaster); ++ YSTR("num"); YINT(mon->num); ++ YSTR("is_selected"); YBOOL(is_selected); ++ ++ YSTR("monitor_geometry"); YMAP( ++ YSTR("x"); YINT(mon->mx); ++ YSTR("y"); YINT(mon->my); ++ YSTR("width"); YINT(mon->mw); ++ YSTR("height"); YINT(mon->mh); ++ ) ++ ++ YSTR("window_geometry"); YMAP( ++ YSTR("x"); YINT(mon->wx); ++ YSTR("y"); YINT(mon->wy); ++ YSTR("width"); YINT(mon->ww); ++ YSTR("height"); YINT(mon->wh); ++ ) ++ ++ YSTR("tagset"); YMAP( ++ YSTR("current"); YINT(mon->tagset[mon->seltags]); ++ YSTR("old"); YINT(mon->tagset[mon->seltags ^ 1]); ++ ) ++ ++ YSTR("tag_state"); dump_tag_state(gen, mon->tagstate); ++ ++ YSTR("clients"); YMAP( ++ YSTR("selected"); YINT(mon->sel ? mon->sel->win : 0); ++ YSTR("stack"); YARR( ++ for (Client* c = mon->stack; c; c = c->snext) ++ YINT(c->win); ++ ) ++ YSTR("all"); YARR( ++ for (Client* c = mon->clients; c; c = c->next) ++ YINT(c->win); ++ ) ++ ) ++ ++ YSTR("layout"); YMAP( ++ YSTR("symbol"); YMAP( ++ YSTR("current"); YSTR(mon->ltsymbol); ++ YSTR("old"); YSTR(mon->lastltsymbol); ++ ) ++ YSTR("address"); YMAP( ++ YSTR("current"); YINT((uintptr_t)mon->lt[mon->sellt]); ++ YSTR("old"); YINT((uintptr_t)mon->lt[mon->sellt ^ 1]); ++ ) ++ ) ++ ++ YSTR("bar"); YMAP( ++ YSTR("y"); YINT(mon->by); ++ YSTR("is_shown"); YBOOL(mon->showbar); ++ YSTR("is_top"); YBOOL(mon->topbar); ++ YSTR("window_id"); YINT(mon->barwin); ++ ) ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_monitors(yajl_gen gen, Monitor *mons, Monitor *selmon) ++{ ++ // clang-format off ++ YARR( ++ for (Monitor *mon = mons; mon; mon = mon->next) { ++ if (mon == selmon) ++ dump_monitor(gen, mon, 1); ++ else ++ dump_monitor(gen, mon, 0); ++ } ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_layouts(yajl_gen gen, const Layout layouts[], const int layouts_len) ++{ ++ // clang-format off ++ YARR( ++ for (int i = 0; i < layouts_len; i++) { ++ YMAP( ++ // Check for a NULL pointer. The cycle layouts patch adds an entry at ++ // the end of the layouts array with a NULL pointer for the symbol ++ YSTR("symbol"); YSTR((layouts[i].symbol ? layouts[i].symbol : "")); ++ YSTR("address"); YINT((uintptr_t)(layouts + i)); ++ ) ++ } ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_tag_state(yajl_gen gen, TagState state) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("selected"); YINT(state.selected); ++ YSTR("occupied"); YINT(state.occupied); ++ YSTR("urgent"); YINT(state.urgent); ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_tag_event(yajl_gen gen, int mon_num, TagState old_state, ++ TagState new_state) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("tag_change_event"); YMAP( ++ YSTR("monitor_number"); YINT(mon_num); ++ YSTR("old_state"); dump_tag_state(gen, old_state); ++ YSTR("new_state"); dump_tag_state(gen, new_state); ++ ) ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_client_focus_change_event(yajl_gen gen, Client *old_client, ++ Client *new_client, int mon_num) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("client_focus_change_event"); YMAP( ++ YSTR("monitor_number"); YINT(mon_num); ++ YSTR("old_win_id"); old_client == NULL ? YNULL() : YINT(old_client->win); ++ YSTR("new_win_id"); new_client == NULL ? YNULL() : YINT(new_client->win); ++ ) ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_layout_change_event(yajl_gen gen, const int mon_num, ++ const char *old_symbol, const Layout *old_layout, ++ const char *new_symbol, const Layout *new_layout) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("layout_change_event"); YMAP( ++ YSTR("monitor_number"); YINT(mon_num); ++ YSTR("old_symbol"); YSTR(old_symbol); ++ YSTR("old_address"); YINT((uintptr_t)old_layout); ++ YSTR("new_symbol"); YSTR(new_symbol); ++ YSTR("new_address"); YINT((uintptr_t)new_layout); ++ ) ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_monitor_focus_change_event(yajl_gen gen, const int last_mon_num, ++ const int new_mon_num) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("monitor_focus_change_event"); YMAP( ++ YSTR("old_monitor_number"); YINT(last_mon_num); ++ YSTR("new_monitor_number"); YINT(new_mon_num); ++ ) ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_focused_title_change_event(yajl_gen gen, const int mon_num, ++ const Window client_id, const char *old_name, ++ const char *new_name) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("focused_title_change_event"); YMAP( ++ YSTR("monitor_number"); YINT(mon_num); ++ YSTR("client_window_id"); YINT(client_id); ++ YSTR("old_name"); YSTR(old_name); ++ YSTR("new_name"); YSTR(new_name); ++ ) ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_client_state(yajl_gen gen, const ClientState *state) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("old_state"); YBOOL(state->oldstate); ++ YSTR("is_fixed"); YBOOL(state->isfixed); ++ YSTR("is_floating"); YBOOL(state->isfloating); ++ YSTR("is_fullscreen"); YBOOL(state->isfullscreen); ++ YSTR("is_urgent"); YBOOL(state->isurgent); ++ YSTR("never_focus"); YBOOL(state->neverfocus); ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_focused_state_change_event(yajl_gen gen, const int mon_num, ++ const Window client_id, ++ const ClientState *old_state, ++ const ClientState *new_state) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("focused_state_change_event"); YMAP( ++ YSTR("monitor_number"); YINT(mon_num); ++ YSTR("client_window_id"); YINT(client_id); ++ YSTR("old_state"); dump_client_state(gen, old_state); ++ YSTR("new_state"); dump_client_state(gen, new_state); ++ ) ++ ) ++ // clang-format on ++ ++ return 0; ++} ++ ++int ++dump_error_message(yajl_gen gen, const char *reason) ++{ ++ // clang-format off ++ YMAP( ++ YSTR("result"); YSTR("error"); ++ YSTR("reason"); YSTR(reason); ++ ) ++ // clang-format on ++ ++ return 0; ++} +diff --git a/yajl_dumps.h b/yajl_dumps.h +new file mode 100644 +index 0000000..ee9948e +--- /dev/null ++++ b/yajl_dumps.h +@@ -0,0 +1,65 @@ ++#ifndef YAJL_DUMPS_H_ ++#define YAJL_DUMPS_H_ ++ ++#include ++#include ++ ++#define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str)) ++#define YINT(num) yajl_gen_integer(gen, num) ++#define YDOUBLE(num) yajl_gen_double(gen, num) ++#define YBOOL(v) yajl_gen_bool(gen, v) ++#define YNULL() yajl_gen_null(gen) ++#define YARR(body) \ ++ { \ ++ yajl_gen_array_open(gen); \ ++ body; \ ++ yajl_gen_array_close(gen); \ ++ } ++#define YMAP(body) \ ++ { \ ++ yajl_gen_map_open(gen); \ ++ body; \ ++ yajl_gen_map_close(gen); \ ++ } ++ ++int dump_tag(yajl_gen gen, const char *name, const int tag_mask); ++ ++int dump_tags(yajl_gen gen, const char *tags[], int tags_len); ++ ++int dump_client(yajl_gen gen, Client *c); ++ ++int dump_monitor(yajl_gen gen, Monitor *mon, int is_selected); ++ ++int dump_monitors(yajl_gen gen, Monitor *mons, Monitor *selmon); ++ ++int dump_layouts(yajl_gen gen, const Layout layouts[], const int layouts_len); ++ ++int dump_tag_state(yajl_gen gen, TagState state); ++ ++int dump_tag_event(yajl_gen gen, int mon_num, TagState old_state, ++ TagState new_state); ++ ++int dump_client_focus_change_event(yajl_gen gen, Client *old_client, ++ Client *new_client, int mon_num); ++ ++int dump_layout_change_event(yajl_gen gen, const int mon_num, ++ const char *old_symbol, const Layout *old_layout, ++ const char *new_symbol, const Layout *new_layout); ++ ++int dump_monitor_focus_change_event(yajl_gen gen, const int last_mon_num, ++ const int new_mon_num); ++ ++int dump_focused_title_change_event(yajl_gen gen, const int mon_num, ++ const Window client_id, ++ const char *old_name, const char *new_name); ++ ++int dump_client_state(yajl_gen gen, const ClientState *state); ++ ++int dump_focused_state_change_event(yajl_gen gen, const int mon_num, ++ const Window client_id, ++ const ClientState *old_state, ++ const ClientState *new_state); ++ ++int dump_error_message(yajl_gen gen, const char *reason); ++ ++#endif // YAJL_DUMPS_H_ +-- +2.29.2 + diff --git a/KleinDwm/source/patches/dwm-keychain-20200729-053e3a2.diff b/KleinDwm/source/patches/dwm-keychain-20200729-053e3a2.diff new file mode 100644 index 0000000..cf3ad28 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/patches/dwm-movestack-20211115-a786211.diff b/KleinDwm/source/patches/dwm-movestack-20211115-a786211.diff new file mode 100644 index 0000000..134abb8 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/patches/dwm-pertag-20200914-61bb8b2.diff b/KleinDwm/source/patches/dwm-pertag-20200914-61bb8b2.diff new file mode 100644 index 0000000..c8d7fbc --- /dev/null +++ b/KleinDwm/source/patches/dwm-pertag-20200914-61bb8b2.diff @@ -0,0 +1,177 @@ +diff --git a/dwm.c b/dwm.c +index 664c527..ac8e4ec 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -111,6 +111,7 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++typedef struct Pertag Pertag; + struct Monitor { + char ltsymbol[16]; + float mfact; +@@ -130,6 +131,7 @@ struct Monitor { + Monitor *next; + Window barwin; + const Layout *lt[2]; ++ Pertag *pertag; + }; + + typedef struct { +@@ -272,6 +274,15 @@ static Window root, wmcheckwin; + /* configuration, allows nested code to access above variables */ + #include "config.h" + ++struct Pertag { ++ unsigned int curtag, prevtag; /* current and previous tag */ ++ int nmasters[LENGTH(tags) + 1]; /* number of windows in master area */ ++ float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */ ++ unsigned int sellts[LENGTH(tags) + 1]; /* selected layouts */ ++ const Layout *ltidxs[LENGTH(tags) + 1][2]; /* matrix of tags and layouts indexes */ ++ int showbars[LENGTH(tags) + 1]; /* display bar for the current tag */ ++}; ++ + /* compile-time check if all tags fit into an unsigned int bit array. */ + struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; + +@@ -632,6 +643,7 @@ Monitor * + createmon(void) + { + Monitor *m; ++ unsigned int i; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; +@@ -642,6 +654,20 @@ createmon(void) + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); ++ m->pertag = ecalloc(1, sizeof(Pertag)); ++ m->pertag->curtag = m->pertag->prevtag = 1; ++ ++ for (i = 0; i <= LENGTH(tags); i++) { ++ m->pertag->nmasters[i] = m->nmaster; ++ m->pertag->mfacts[i] = m->mfact; ++ ++ m->pertag->ltidxs[i][0] = m->lt[0]; ++ m->pertag->ltidxs[i][1] = m->lt[1]; ++ m->pertag->sellts[i] = m->sellt; ++ ++ m->pertag->showbars[i] = m->showbar; ++ } ++ + return m; + } + +@@ -967,7 +993,7 @@ grabkeys(void) + void + incnmaster(const Arg *arg) + { +- selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); ++ selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag] = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); + } + +@@ -1502,9 +1528,9 @@ void + setlayout(const Arg *arg) + { + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) +- selmon->sellt ^= 1; ++ selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag] ^= 1; + if (arg && arg->v) +- selmon->lt[selmon->sellt] = (Layout *)arg->v; ++ selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); + if (selmon->sel) + arrange(selmon); +@@ -1523,7 +1549,7 @@ setmfact(const Arg *arg) + f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + if (f < 0.05 || f > 0.95) + return; +- selmon->mfact = f; ++ selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag] = f; + arrange(selmon); + } + +@@ -1702,7 +1728,7 @@ tile(Monitor *m) + void + togglebar(const Arg *arg) + { +- selmon->showbar = !selmon->showbar; ++ selmon->showbar = selmon->pertag->showbars[selmon->pertag->curtag] = !selmon->showbar; + updatebarpos(selmon); + XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); + arrange(selmon); +@@ -1741,9 +1767,33 @@ void + toggleview(const Arg *arg) + { + unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); ++ int i; + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; ++ ++ if (newtagset == ~0) { ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ selmon->pertag->curtag = 0; ++ } ++ ++ /* test if the user did not select the same tag */ ++ if (!(newtagset & 1 << (selmon->pertag->curtag - 1))) { ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ for (i = 0; !(newtagset & 1 << i); i++) ; ++ selmon->pertag->curtag = i + 1; ++ } ++ ++ /* apply settings for this view */ ++ selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; ++ selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; ++ selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; ++ selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; ++ selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; ++ ++ if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) ++ togglebar(NULL); ++ + focus(NULL); + arrange(selmon); + } +@@ -2038,11 +2088,37 @@ updatewmhints(Client *c) + void + view(const Arg *arg) + { ++ int i; ++ unsigned int tmptag; ++ + if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ +- if (arg->ui & TAGMASK) ++ if (arg->ui & TAGMASK) { + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ ++ if (arg->ui == ~0) ++ selmon->pertag->curtag = 0; ++ else { ++ for (i = 0; !(arg->ui & 1 << i); i++) ; ++ selmon->pertag->curtag = i + 1; ++ } ++ } else { ++ tmptag = selmon->pertag->prevtag; ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ selmon->pertag->curtag = tmptag; ++ } ++ ++ selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; ++ selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; ++ selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; ++ selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; ++ selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; ++ ++ if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) ++ togglebar(NULL); ++ + focus(NULL); + arrange(selmon); + } diff --git a/KleinDwm/source/patches/dwm-rainbowtags-6.2.diff b/KleinDwm/source/patches/dwm-rainbowtags-6.2.diff new file mode 100644 index 0000000..6c31062 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/patches/dwm-restartsig-20180523-6.2.diff b/KleinDwm/source/patches/dwm-restartsig-20180523-6.2.diff new file mode 100644 index 0000000..f1f8680 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/patches/dwm-status2d-systray-6.3.diff b/KleinDwm/source/patches/dwm-status2d-systray-6.3.diff new file mode 100644 index 0000000..c79ef49 --- /dev/null +++ b/KleinDwm/source/patches/dwm-status2d-systray-6.3.diff @@ -0,0 +1,888 @@ +diff --git a/config.def.h b/config.def.h +index a2ac963..86fcc84 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -3,8 +3,13 @@ + /* appearance */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const unsigned int snap = 32; /* snap pixel */ +-static const int showbar = 1; /* 0 means no bar */ +-static const int topbar = 1; /* 0 means bottom bar */ ++static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */ ++static const unsigned int systrayonleft = 0; /* 0: systray in the right corner, >0: systray on left of status text */ ++static const unsigned int systrayspacing = 2; /* systray spacing */ ++static const int systraypinningfailfirst = 1; /* 1: if pinning fails, display systray on the first monitor, False: display systray on the last monitor*/ ++static const int showsystray = 1; /* 0 means no systray */ ++static const int showbar = 1; /* 0 means no bar */ ++static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = { "monospace:size=10" }; + static const char dmenufont[] = "monospace:size=10"; + static const char col_gray1[] = "#222222"; +@@ -101,8 +106,8 @@ static Key keys[] = { + /* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ + static Button buttons[] = { + /* click event mask button function argument */ +- { ClkLtSymbol, 0, Button1, setlayout, {0} }, +- { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, ++ { ClkTagBar, MODKEY, Button1, tag, {0} }, ++ { ClkTagBar, MODKEY, Button3, toggletag, {0} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, +diff --git a/dwm.c b/dwm.c +index a96f33c..8153bfe 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -57,12 +57,27 @@ + #define TAGMASK ((1 << LENGTH(tags)) - 1) + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + ++#define SYSTEM_TRAY_REQUEST_DOCK 0 ++/* XEMBED messages */ ++#define XEMBED_EMBEDDED_NOTIFY 0 ++#define XEMBED_WINDOW_ACTIVATE 1 ++#define XEMBED_FOCUS_IN 4 ++#define XEMBED_MODALITY_ON 10 ++#define XEMBED_MAPPED (1 << 0) ++#define XEMBED_WINDOW_ACTIVATE 1 ++#define XEMBED_WINDOW_DEACTIVATE 2 ++#define VERSION_MAJOR 0 ++#define VERSION_MINOR 0 ++#define XEMBED_EMBEDDED_VERSION (VERSION_MAJOR << 16) | VERSION_MINOR ++ + /* enums */ + enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ + enum { SchemeNorm, SchemeSel }; /* color schemes */ + enum { NetSupported, NetWMName, NetWMState, NetWMCheck, ++ NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayOrientationHorz, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ ++enum { Manager, Xembed, XembedInfo, XLast }; /* Xembed atoms */ + enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ + enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ +@@ -141,6 +156,12 @@ typedef struct { + int monitor; + } Rule; + ++typedef struct Systray Systray; ++struct Systray { ++ Window win; ++ Client *icons; ++}; ++ + /* function declarations */ + static void applyrules(Client *c); + static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +@@ -163,6 +184,7 @@ static void detachstack(Client *c); + static Monitor *dirtomon(int dir); + static void drawbar(Monitor *m); + static void drawbars(void); ++static int drawstatusbar(Monitor *m, int bh, char* text); + static void enternotify(XEvent *e); + static void expose(XEvent *e); + static void focus(Client *c); +@@ -172,6 +194,7 @@ static void focusstack(const Arg *arg); + static Atom getatomprop(Client *c, Atom prop); + static int getrootptr(int *x, int *y); + static long getstate(Window w); ++static unsigned int getsystraywidth(); + static int gettextprop(Window w, Atom atom, char *text, unsigned int size); + static void grabbuttons(Client *c, int focused); + static void grabkeys(void); +@@ -189,13 +212,16 @@ static void pop(Client *); + static void propertynotify(XEvent *e); + static void quit(const Arg *arg); + static Monitor *recttomon(int x, int y, int w, int h); ++static void removesystrayicon(Client *i); + static void resize(Client *c, int x, int y, int w, int h, int interact); ++static void resizebarwin(Monitor *m); + static void resizeclient(Client *c, int x, int y, int w, int h); + static void resizemouse(const Arg *arg); ++static void resizerequest(XEvent *e); + static void restack(Monitor *m); + static void run(void); + static void scan(void); +-static int sendevent(Client *c, Atom proto); ++static int sendevent(Window w, Atom proto, int m, long d0, long d1, long d2, long d3, long d4); + static void sendmon(Client *c, Monitor *m); + static void setclientstate(Client *c, long state); + static void setfocus(Client *c); +@@ -207,6 +233,7 @@ static void seturgent(Client *c, int urg); + static void showhide(Client *c); + static void sigchld(int unused); + static void spawn(const Arg *arg); ++static Monitor *systraytomon(Monitor *m); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *); +@@ -224,20 +251,25 @@ static int updategeom(void); + static void updatenumlockmask(void); + static void updatesizehints(Client *c); + static void updatestatus(void); ++static void updatesystray(void); ++static void updatesystrayicongeom(Client *i, int w, int h); ++static void updatesystrayiconstate(Client *i, XPropertyEvent *ev); + static void updatetitle(Client *c); + static void updatewindowtype(Client *c); + static void updatewmhints(Client *c); + static void view(const Arg *arg); + static Client *wintoclient(Window w); + static Monitor *wintomon(Window w); ++static Client *wintosystrayicon(Window w); + static int xerror(Display *dpy, XErrorEvent *ee); + static int xerrordummy(Display *dpy, XErrorEvent *ee); + static int xerrorstart(Display *dpy, XErrorEvent *ee); + static void zoom(const Arg *arg); + + /* variables */ ++static Systray *systray = NULL; + static const char broken[] = "broken"; +-static char stext[256]; ++static char stext[1024]; + static int screen; + static int sw, sh; /* X display screen geometry width, height */ + static int bh, blw = 0; /* bar geometry */ +@@ -258,9 +290,10 @@ static void (*handler[LASTEvent]) (XEvent *) = { + [MapRequest] = maprequest, + [MotionNotify] = motionnotify, + [PropertyNotify] = propertynotify, ++ [ResizeRequest] = resizerequest, + [UnmapNotify] = unmapnotify + }; +-static Atom wmatom[WMLast], netatom[NetLast]; ++static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast]; + static int running = 1; + static Cur *cursor[CurLast]; + static Clr **scheme; +@@ -440,7 +473,7 @@ buttonpress(XEvent *e) + arg.ui = 1 << i; + } else if (ev->x < x + blw) + click = ClkLtSymbol; +- else if (ev->x > selmon->ww - (int)TEXTW(stext)) ++ else if (ev->x > selmon->ww - (int)TEXTW(stext) - getsystraywidth()) + click = ClkStatusText; + else + click = ClkWinTitle; +@@ -483,9 +516,16 @@ cleanup(void) + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); +- for (i = 0; i < CurLast; i++) ++ ++ if (showsystray) { ++ XUnmapWindow(dpy, systray->win); ++ XDestroyWindow(dpy, systray->win); ++ free(systray); ++ } ++ ++ for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); +- for (i = 0; i < LENGTH(colors); i++) ++ for (i = 0; i < LENGTH(colors) + 1; i++) + free(scheme[i]); + XDestroyWindow(dpy, wmcheckwin); + drw_free(drw); +@@ -513,9 +553,58 @@ cleanupmon(Monitor *mon) + void + clientmessage(XEvent *e) + { ++ XWindowAttributes wa; ++ XSetWindowAttributes swa; + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + ++ if (showsystray && cme->window == systray->win && cme->message_type == netatom[NetSystemTrayOP]) { ++ /* add systray icons */ ++ if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) { ++ if (!(c = (Client *)calloc(1, sizeof(Client)))) ++ die("fatal: could not malloc() %u bytes\n", sizeof(Client)); ++ if (!(c->win = cme->data.l[2])) { ++ free(c); ++ return; ++ } ++ c->mon = selmon; ++ c->next = systray->icons; ++ systray->icons = c; ++ if (!XGetWindowAttributes(dpy, c->win, &wa)) { ++ /* use sane defaults */ ++ wa.width = bh; ++ wa.height = bh; ++ wa.border_width = 0; ++ } ++ c->x = c->oldx = c->y = c->oldy = 0; ++ c->w = c->oldw = wa.width; ++ c->h = c->oldh = wa.height; ++ c->oldbw = wa.border_width; ++ c->bw = 0; ++ c->isfloating = True; ++ /* reuse tags field as mapped status */ ++ c->tags = 1; ++ updatesizehints(c); ++ updatesystrayicongeom(c, wa.width, wa.height); ++ XAddToSaveSet(dpy, c->win); ++ XSelectInput(dpy, c->win, StructureNotifyMask | PropertyChangeMask | ResizeRedirectMask); ++ XReparentWindow(dpy, c->win, systray->win, 0, 0); ++ /* use parents background color */ ++ swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; ++ XChangeWindowAttributes(dpy, c->win, CWBackPixel, &swa); ++ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0 , systray->win, XEMBED_EMBEDDED_VERSION); ++ /* FIXME not sure if I have to send these events, too */ ++ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_FOCUS_IN, 0 , systray->win, XEMBED_EMBEDDED_VERSION); ++ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0 , systray->win, XEMBED_EMBEDDED_VERSION); ++ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_MODALITY_ON, 0 , systray->win, XEMBED_EMBEDDED_VERSION); ++ XSync(dpy, False); ++ resizebarwin(selmon); ++ updatesystray(); ++ setclientstate(c, NormalState); ++ } ++ return; ++ } ++ + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { +@@ -568,7 +657,7 @@ configurenotify(XEvent *e) + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); +- XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); ++ resizebarwin(m); + } + focus(NULL); + arrange(NULL); +@@ -653,6 +742,11 @@ destroynotify(XEvent *e) + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); ++ else if ((c = wintosystrayicon(ev->window))) { ++ removesystrayicon(c); ++ resizebarwin(selmon); ++ updatesystray(); ++ } + } + + void +@@ -693,10 +787,119 @@ dirtomon(int dir) + return m; + } + ++int ++drawstatusbar(Monitor *m, int bh, char* stext) { ++ int ret, i, w, x, len; ++ short isCode = 0; ++ char *text; ++ char *p; ++ ++ len = strlen(stext) + 1 ; ++ if (!(text = (char*) malloc(sizeof(char)*len))) ++ die("malloc"); ++ p = text; ++ memcpy(text, stext, len); ++ ++ /* compute width of the status text */ ++ w = 0; ++ i = -1; ++ while (text[++i]) { ++ if (text[i] == '^') { ++ if (!isCode) { ++ isCode = 1; ++ text[i] = '\0'; ++ w += TEXTW(text) - lrpad; ++ text[i] = '^'; ++ if (text[++i] == 'f') ++ w += atoi(text + ++i); ++ } else { ++ isCode = 0; ++ text = text + i + 1; ++ i = -1; ++ } ++ } ++ } ++ if (!isCode) ++ w += TEXTW(text) - lrpad; ++ else ++ isCode = 0; ++ text = p; ++ ++ w += 2; /* 1px padding on both sides */ ++ ret = m->ww - w; ++ x = m->ww - w - getsystraywidth(); ++ ++ drw_setscheme(drw, scheme[LENGTH(colors)]); ++ drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; ++ drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; ++ drw_rect(drw, x, 0, w, bh, 1, 1); ++ x++; ++ ++ /* process status text */ ++ i = -1; ++ while (text[++i]) { ++ if (text[i] == '^' && !isCode) { ++ isCode = 1; ++ ++ text[i] = '\0'; ++ w = TEXTW(text) - lrpad; ++ drw_text(drw, x, 0, w, bh, 0, text, 0); ++ ++ x += w; ++ ++ /* process code */ ++ while (text[++i] != '^') { ++ if (text[i] == 'c') { ++ char buf[8]; ++ memcpy(buf, (char*)text+i+1, 7); ++ buf[7] = '\0'; ++ drw_clr_create(drw, &drw->scheme[ColFg], buf); ++ i += 7; ++ } else if (text[i] == 'b') { ++ char buf[8]; ++ memcpy(buf, (char*)text+i+1, 7); ++ buf[7] = '\0'; ++ drw_clr_create(drw, &drw->scheme[ColBg], buf); ++ i += 7; ++ } else if (text[i] == 'd') { ++ drw->scheme[ColFg] = scheme[SchemeNorm][ColFg]; ++ drw->scheme[ColBg] = scheme[SchemeNorm][ColBg]; ++ } else if (text[i] == 'r') { ++ int rx = atoi(text + ++i); ++ while (text[++i] != ','); ++ int ry = atoi(text + ++i); ++ while (text[++i] != ','); ++ int rw = atoi(text + ++i); ++ while (text[++i] != ','); ++ int rh = atoi(text + ++i); ++ ++ drw_rect(drw, rx + x, ry, rw, rh, 1, 0); ++ } else if (text[i] == 'f') { ++ x += atoi(text + ++i); ++ } ++ } ++ ++ text = text + i + 1; ++ i=-1; ++ isCode = 0; ++ } ++ } ++ ++ if (!isCode) { ++ w = TEXTW(text) - lrpad; ++ drw_text(drw, x, 0, w, bh, 0, text, 0); ++ } ++ ++ drw_setscheme(drw, scheme[SchemeNorm]); ++ free(p); ++ ++ return ret; ++} ++ + void + drawbar(Monitor *m) + { +- int x, w, tw = 0; ++ int x, w, tw = 0, stw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + unsigned int i, occ = 0, urg = 0; +@@ -705,13 +908,15 @@ drawbar(Monitor *m) + if (!m->showbar) + return; + ++ if(showsystray && m == systraytomon(m) && !systrayonleft) ++ stw = getsystraywidth(); ++ + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ +- drw_setscheme(drw, scheme[SchemeNorm]); +- tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ +- drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); ++ tw = m->ww - drawstatusbar(m, bh, stext); + } + ++ resizebarwin(m); + for (c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) +@@ -732,7 +937,7 @@ drawbar(Monitor *m) + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + +- if ((w = m->ww - tw - x) > bh) { ++ if ((w = m->ww - tw - stw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); +@@ -743,7 +948,7 @@ drawbar(Monitor *m) + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } +- drw_map(drw, m->barwin, 0, 0, m->ww, bh); ++ drw_map(drw, m->barwin, 0, 0, m->ww - stw, bh); + } + + void +@@ -780,8 +985,11 @@ expose(XEvent *e) + Monitor *m; + XExposeEvent *ev = &e->xexpose; + +- if (ev->count == 0 && (m = wintomon(ev->window))) ++ if (ev->count == 0 && (m = wintomon(ev->window))) { + drawbar(m); ++ if (m == selmon) ++ updatesystray(); ++ } + } + + void +@@ -867,9 +1075,17 @@ getatomprop(Client *c, Atom prop) + unsigned char *p = NULL; + Atom da, atom = None; + +- if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, ++ /* FIXME getatomprop should return the number of items and a pointer to ++ * the stored data instead of this workaround */ ++ Atom req = XA_ATOM; ++ if (prop == xatom[XembedInfo]) ++ req = xatom[XembedInfo]; ++ ++ if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, req, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; ++ if (da == xatom[XembedInfo] && dl == 2) ++ atom = ((Atom *)p)[1]; + XFree(p); + } + return atom; +@@ -903,6 +1119,16 @@ getstate(Window w) + return result; + } + ++unsigned int ++getsystraywidth() ++{ ++ unsigned int w = 0; ++ Client *i; ++ if(showsystray) ++ for(i = systray->icons; i; w += i->w + systrayspacing, i = i->next) ; ++ return w ? w + systrayspacing : 1; ++} ++ + int + gettextprop(Window w, Atom atom, char *text, unsigned int size) + { +@@ -1007,7 +1233,8 @@ killclient(const Arg *arg) + { + if (!selmon->sel) + return; +- if (!sendevent(selmon->sel, wmatom[WMDelete])) { ++ ++ if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0 , 0, 0)) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); +@@ -1096,6 +1323,13 @@ maprequest(XEvent *e) + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + ++ Client *i; ++ if ((i = wintosystrayicon(ev->window))) { ++ sendevent(i->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0, systray->win, XEMBED_EMBEDDED_VERSION); ++ resizebarwin(selmon); ++ updatesystray(); ++ } ++ + if (!XGetWindowAttributes(dpy, ev->window, &wa)) + return; + if (wa.override_redirect) +@@ -1219,7 +1453,18 @@ propertynotify(XEvent *e) + Window trans; + XPropertyEvent *ev = &e->xproperty; + +- if ((ev->window == root) && (ev->atom == XA_WM_NAME)) ++ if ((c = wintosystrayicon(ev->window))) { ++ if (ev->atom == XA_WM_NORMAL_HINTS) { ++ updatesizehints(c); ++ updatesystrayicongeom(c, c->w, c->h); ++ } ++ else ++ updatesystrayiconstate(c, ev); ++ resizebarwin(selmon); ++ updatesystray(); ++ } ++ ++ if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ +@@ -1269,6 +1514,19 @@ recttomon(int x, int y, int w, int h) + return r; + } + ++void ++removesystrayicon(Client *i) ++{ ++ Client **ii; ++ ++ if (!showsystray || !i) ++ return; ++ for (ii = &systray->icons; *ii && *ii != i; ii = &(*ii)->next); ++ if (ii) ++ *ii = i->next; ++ free(i); ++} ++ + void + resize(Client *c, int x, int y, int w, int h, int interact) + { +@@ -1276,6 +1534,14 @@ resize(Client *c, int x, int y, int w, int h, int interact) + resizeclient(c, x, y, w, h); + } + ++void ++resizebarwin(Monitor *m) { ++ unsigned int w = m->ww; ++ if (showsystray && m == systraytomon(m) && !systrayonleft) ++ w -= getsystraywidth(); ++ XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, w, bh); ++} ++ + void + resizeclient(Client *c, int x, int y, int w, int h) + { +@@ -1348,6 +1614,19 @@ resizemouse(const Arg *arg) + } + } + ++void ++resizerequest(XEvent *e) ++{ ++ XResizeRequestEvent *ev = &e->xresizerequest; ++ Client *i; ++ ++ if ((i = wintosystrayicon(ev->window))) { ++ updatesystrayicongeom(i, ev->width, ev->height); ++ resizebarwin(selmon); ++ updatesystray(); ++ } ++} ++ + void + restack(Monitor *m) + { +@@ -1437,26 +1716,37 @@ setclientstate(Client *c, long state) + } + + int +-sendevent(Client *c, Atom proto) ++sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3, long d4) + { + int n; +- Atom *protocols; ++ Atom *protocols, mt; + int exists = 0; + XEvent ev; + +- if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { +- while (!exists && n--) +- exists = protocols[n] == proto; +- XFree(protocols); ++ if (proto == wmatom[WMTakeFocus] || proto == wmatom[WMDelete]) { ++ mt = wmatom[WMProtocols]; ++ if (XGetWMProtocols(dpy, w, &protocols, &n)) { ++ while (!exists && n--) ++ exists = protocols[n] == proto; ++ XFree(protocols); ++ } + } ++ else { ++ exists = True; ++ mt = proto; ++ } ++ + if (exists) { + ev.type = ClientMessage; +- ev.xclient.window = c->win; +- ev.xclient.message_type = wmatom[WMProtocols]; ++ ev.xclient.window = w; ++ ev.xclient.message_type = mt; + ev.xclient.format = 32; +- ev.xclient.data.l[0] = proto; +- ev.xclient.data.l[1] = CurrentTime; +- XSendEvent(dpy, c->win, False, NoEventMask, &ev); ++ ev.xclient.data.l[0] = d0; ++ ev.xclient.data.l[1] = d1; ++ ev.xclient.data.l[2] = d2; ++ ev.xclient.data.l[3] = d3; ++ ev.xclient.data.l[4] = d4; ++ XSendEvent(dpy, w, False, mask, &ev); + } + return exists; + } +@@ -1470,7 +1760,7 @@ setfocus(Client *c) + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(c->win), 1); + } +- sendevent(c, wmatom[WMTakeFocus]); ++ sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0); + } + + void +@@ -1558,22 +1848,32 @@ setup(void) + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); +- netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); +- netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); ++ netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); ++ netatom[NetSystemTray] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", False); ++ netatom[NetSystemTrayOP] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False); ++ netatom[NetSystemTrayOrientation] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION", False); ++ netatom[NetSystemTrayOrientationHorz] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION_HORZ", False); ++ netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); +- /* init cursors */ ++ xatom[Manager] = XInternAtom(dpy, "MANAGER", False); ++ xatom[Xembed] = XInternAtom(dpy, "_XEMBED", False); ++ xatom[XembedInfo] = XInternAtom(dpy, "_XEMBED_INFO", False); ++ /* init cursors */ + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); + /* init appearance */ +- scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); ++ scheme = ecalloc(LENGTH(colors) + 1, sizeof(Clr *)); ++ scheme[LENGTH(colors)] = drw_scm_create(drw, colors[0], 3); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); ++ /* init system tray */ ++ updatesystray(); + /* init bars */ + updatebars(); + updatestatus(); +@@ -1707,7 +2007,18 @@ togglebar(const Arg *arg) + { + selmon->showbar = !selmon->showbar; + updatebarpos(selmon); +- XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); ++ resizebarwin(selmon); ++ if (showsystray) { ++ XWindowChanges wc; ++ if (!selmon->showbar) ++ wc.y = -bh; ++ else if (selmon->showbar) { ++ wc.y = 0; ++ if (!selmon->topbar) ++ wc.y = selmon->mh - bh; ++ } ++ XConfigureWindow(dpy, systray->win, CWY, &wc); ++ } + arrange(selmon); + } + +@@ -1802,11 +2113,18 @@ unmapnotify(XEvent *e) + else + unmanage(c, 0); + } ++ else if ((c = wintosystrayicon(ev->window))) { ++ /* KLUDGE! sometimes icons occasionally unmap their windows, but do ++ * _not_ destroy them. We map those windows back */ ++ XMapRaised(dpy, c->win); ++ updatesystray(); ++ } + } + + void + updatebars(void) + { ++ unsigned int w; + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, +@@ -1817,10 +2135,15 @@ updatebars(void) + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; +- m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), ++ w = m->ww; ++ if (showsystray && m == systraytomon(m)) ++ w -= getsystraywidth(); ++ m->barwin = XCreateWindow(dpy, root, m->wx, m->by, w, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); ++ if (showsystray && m == systraytomon(m)) ++ XMapRaised(dpy, systray->win); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +@@ -1996,6 +2319,125 @@ updatestatus(void) + if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) + strcpy(stext, "dwm-"VERSION); + drawbar(selmon); ++ updatesystray(); ++} ++ ++ ++void ++updatesystrayicongeom(Client *i, int w, int h) ++{ ++ if (i) { ++ i->h = bh; ++ if (w == h) ++ i->w = bh; ++ else if (h == bh) ++ i->w = w; ++ else ++ i->w = (int) ((float)bh * ((float)w / (float)h)); ++ applysizehints(i, &(i->x), &(i->y), &(i->w), &(i->h), False); ++ /* force icons into the systray dimensions if they don't want to */ ++ if (i->h > bh) { ++ if (i->w == i->h) ++ i->w = bh; ++ else ++ i->w = (int) ((float)bh * ((float)i->w / (float)i->h)); ++ i->h = bh; ++ } ++ } ++} ++ ++void ++updatesystrayiconstate(Client *i, XPropertyEvent *ev) ++{ ++ long flags; ++ int code = 0; ++ ++ if (!showsystray || !i || ev->atom != xatom[XembedInfo] || ++ !(flags = getatomprop(i, xatom[XembedInfo]))) ++ return; ++ ++ if (flags & XEMBED_MAPPED && !i->tags) { ++ i->tags = 1; ++ code = XEMBED_WINDOW_ACTIVATE; ++ XMapRaised(dpy, i->win); ++ setclientstate(i, NormalState); ++ } ++ else if (!(flags & XEMBED_MAPPED) && i->tags) { ++ i->tags = 0; ++ code = XEMBED_WINDOW_DEACTIVATE; ++ XUnmapWindow(dpy, i->win); ++ setclientstate(i, WithdrawnState); ++ } ++ else ++ return; ++ sendevent(i->win, xatom[Xembed], StructureNotifyMask, CurrentTime, code, 0, ++ systray->win, XEMBED_EMBEDDED_VERSION); ++} ++ ++void ++updatesystray(void) ++{ ++ XSetWindowAttributes wa; ++ XWindowChanges wc; ++ Client *i; ++ Monitor *m = systraytomon(NULL); ++ unsigned int x = m->mx + m->mw; ++ unsigned int sw = TEXTW(stext) - lrpad + systrayspacing; ++ unsigned int w = 1; ++ ++ if (!showsystray) ++ return; ++ if (systrayonleft) ++ x -= sw + lrpad / 2; ++ if (!systray) { ++ /* init systray */ ++ if (!(systray = (Systray *)calloc(1, sizeof(Systray)))) ++ die("fatal: could not malloc() %u bytes\n", sizeof(Systray)); ++ systray->win = XCreateSimpleWindow(dpy, root, x, m->by, w, bh, 0, 0, scheme[SchemeSel][ColBg].pixel); ++ wa.event_mask = ButtonPressMask | ExposureMask; ++ wa.override_redirect = True; ++ wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; ++ XSelectInput(dpy, systray->win, SubstructureNotifyMask); ++ XChangeProperty(dpy, systray->win, netatom[NetSystemTrayOrientation], XA_CARDINAL, 32, ++ PropModeReplace, (unsigned char *)&netatom[NetSystemTrayOrientationHorz], 1); ++ XChangeWindowAttributes(dpy, systray->win, CWEventMask|CWOverrideRedirect|CWBackPixel, &wa); ++ XMapRaised(dpy, systray->win); ++ XSetSelectionOwner(dpy, netatom[NetSystemTray], systray->win, CurrentTime); ++ if (XGetSelectionOwner(dpy, netatom[NetSystemTray]) == systray->win) { ++ sendevent(root, xatom[Manager], StructureNotifyMask, CurrentTime, netatom[NetSystemTray], systray->win, 0, 0); ++ XSync(dpy, False); ++ } ++ else { ++ fprintf(stderr, "dwm: unable to obtain system tray.\n"); ++ free(systray); ++ systray = NULL; ++ return; ++ } ++ } ++ for (w = 0, i = systray->icons; i; i = i->next) { ++ /* make sure the background color stays the same */ ++ wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; ++ XChangeWindowAttributes(dpy, i->win, CWBackPixel, &wa); ++ XMapRaised(dpy, i->win); ++ w += systrayspacing; ++ i->x = w; ++ XMoveResizeWindow(dpy, i->win, i->x, 0, i->w, i->h); ++ w += i->w; ++ if (i->mon != m) ++ i->mon = m; ++ } ++ w = w ? w + systrayspacing : 1; ++ x -= w; ++ XMoveResizeWindow(dpy, systray->win, x, m->by, w, bh); ++ wc.x = x; wc.y = m->by; wc.width = w; wc.height = bh; ++ wc.stack_mode = Above; wc.sibling = m->barwin; ++ XConfigureWindow(dpy, systray->win, CWX|CWY|CWWidth|CWHeight|CWSibling|CWStackMode, &wc); ++ XMapWindow(dpy, systray->win); ++ XMapSubwindows(dpy, systray->win); ++ /* redraw background */ ++ XSetForeground(dpy, drw->gc, scheme[SchemeNorm][ColBg].pixel); ++ XFillRectangle(dpy, systray->win, drw->gc, 0, 0, w, bh); ++ XSync(dpy, False); + } + + void +@@ -2063,6 +2505,16 @@ wintoclient(Window w) + return NULL; + } + ++Client * ++wintosystrayicon(Window w) { ++ Client *i = NULL; ++ ++ if (!showsystray || !w) ++ return i; ++ for (i = systray->icons; i && i->win != w; i = i->next) ; ++ return i; ++} ++ + Monitor * + wintomon(Window w) + { +@@ -2116,6 +2568,22 @@ xerrorstart(Display *dpy, XErrorEvent *ee) + return -1; + } + ++Monitor * ++systraytomon(Monitor *m) { ++ Monitor *t; ++ int i, n; ++ if(!systraypinning) { ++ if(!m) ++ return selmon; ++ return m == selmon ? m : NULL; ++ } ++ for(n = 1, t = mons; t && t->next; n++, t = t->next) ; ++ for(i = 1, t = mons; t && t->next && i < systraypinning; i++, t = t->next) ; ++ if(systraypinningfailfirst && n < systraypinning) ++ return mons; ++ return t; ++} ++ + void + zoom(const Arg *arg) + { diff --git a/KleinDwm/source/patches/dwm-titlecolor-20210815-ed3ab6b4.diff b/KleinDwm/source/patches/dwm-titlecolor-20210815-ed3ab6b4.diff new file mode 100644 index 0000000..f0e5b36 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/patches/dwm-winicon-6.3-v2.1.diff b/KleinDwm/source/patches/dwm-winicon-6.3-v2.1.diff new file mode 100644 index 0000000..4278431 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/tags b/KleinDwm/source/tags new file mode 100644 index 0000000..d50b9f4 --- /dev/null +++ b/KleinDwm/source/tags @@ -0,0 +1,496 @@ +!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_OUTPUT_EXCMD mixed /number, pattern, mixed, or combineV2/ +!_TAG_OUTPUT_FILESEP slash /slash or backslash/ +!_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/ +!_TAG_PATTERN_LENGTH_LIMIT 96 /0 for no limit/ +!_TAG_PROC_CWD /home/klein/KleinWindowManagement/KleinDwm/source/ // +!_TAG_PROGRAM_AUTHOR Universal Ctags Team // +!_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/ +!_TAG_PROGRAM_URL https://ctags.io/ /official site/ +!_TAG_PROGRAM_VERSION 5.9.0 // +${OBJ} Makefile /^${OBJ}: config.h config.mk$/;" t +.c.o Makefile /^.c.o:$/;" t +Arg dwm.c /^} Arg;$/;" t typeref:union:__anon0f53a99e070a file: +BETWEEN util.h /^#define BETWEEN(/;" d +BUGS dwm.1 /^.SH BUGS$/;" s title:DWM +BUTTONMASK dwm.c /^#define BUTTONMASK /;" d file: +Button dwm.c /^} Button;$/;" t typeref:struct:__anon0f53a99e0808 file: +CC config.mk /^CC = cc$/;" m +CFLAGS config.mk /^CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS}$/;" m +CLEANMASK dwm.c /^#define CLEANMASK(/;" d file: +CPPFLAGS config.mk /^CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\\"${VERSION}\\" /;" m +CUSTOMIZATION dwm.1 /^.SH CUSTOMIZATION$/;" s title:DWM +CenterThisWindow dwm.c /^ int CenterThisWindow;$/;" m struct:__anon0f53a99e0b08 typeref:typename:int file: +CenterThisWindow dwm.c /^ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, CenterThisWindow;$/;" m struct:Client typeref:typename:int file: +Client dwm.c /^struct Client {$/;" s file: +Client dwm.c /^typedef struct Client Client;$/;" t typeref:struct:Client file: +ClkClientWin dwm.c /^ ClkClientWin, ClkRootWin, ClkLast }; \/* clicks *\/$/;" e enum:__anon0f53a99e0603 file: +ClkLast dwm.c /^ ClkClientWin, ClkRootWin, ClkLast }; \/* clicks *\/$/;" e enum:__anon0f53a99e0603 file: +ClkLtSymbol dwm.c /^enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,$/;" e enum:__anon0f53a99e0603 file: +ClkRootWin dwm.c /^ ClkClientWin, ClkRootWin, ClkLast }; \/* clicks *\/$/;" e enum:__anon0f53a99e0603 file: +ClkStatusText dwm.c /^enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,$/;" e enum:__anon0f53a99e0603 file: +ClkTagBar dwm.c /^enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,$/;" e enum:__anon0f53a99e0603 file: +ClkWinTitle dwm.c /^enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,$/;" e enum:__anon0f53a99e0603 file: +Clr drw.h /^typedef XftColor Clr;$/;" t typeref:typename:XftColor +ColBg drw.h /^enum { ColFg, ColBg, ColBorder }; \/* Clr scheme index *\/$/;" e enum:__anon0f5116480203 +ColBorder drw.h /^enum { ColFg, ColBg, ColBorder }; \/* Clr scheme index *\/$/;" e enum:__anon0f5116480203 +ColFg drw.h /^enum { ColFg, ColBg, ColBorder }; \/* Clr scheme index *\/$/;" e enum:__anon0f5116480203 +Cur drw.h /^} Cur;$/;" t typeref:struct:__anon0f5116480108 +CurLast dwm.c /^enum { CurNormal, CurResize, CurMove, CurLast }; \/* cursor *\/$/;" e enum:__anon0f53a99e0103 file: +CurMove dwm.c /^enum { CurNormal, CurResize, CurMove, CurLast }; \/* cursor *\/$/;" e enum:__anon0f53a99e0103 file: +CurNormal dwm.c /^enum { CurNormal, CurResize, CurMove, CurLast }; \/* cursor *\/$/;" e enum:__anon0f53a99e0103 file: +CurResize dwm.c /^enum { CurNormal, CurResize, CurMove, CurLast }; \/* cursor *\/$/;" e enum:__anon0f53a99e0103 file: +DESCRIPTION dwm.1 /^.SH DESCRIPTION$/;" s title:DWM +DWM dwm.1 /^.TH DWM 1 dwm\\-VERSION$/;" t +Drw drw.h /^} Drw;$/;" t typeref:struct:__anon0f5116480308 +FILES dwm.1 /^.SH FILES$/;" s title:DWM +FREETYPEINC config.mk /^FREETYPEINC = \/usr\/include\/freetype2$/;" m +FREETYPELIBS config.mk /^FREETYPELIBS = -lfontconfig -lXft$/;" m +Fnt drw.h /^typedef struct Fnt {$/;" s +Fnt drw.h /^} Fnt;$/;" t typeref:struct:Fnt +HEIGHT dwm.c /^#define HEIGHT(/;" d file: +ICONSIZE config.def.h /^#define ICONSIZE /;" d +ICONSIZE config.h /^#define ICONSIZE /;" d +ICONSPACING config.def.h /^#define ICONSPACING /;" d +ICONSPACING config.h /^#define ICONSPACING /;" d +INCS config.mk /^INCS = -I${X11INC} -I${FREETYPEINC}$/;" m +INTERSECT dwm.c /^#define INTERSECT(/;" d file: +ISSUES dwm.1 /^.SH ISSUES$/;" s title:DWM +ISVISIBLE dwm.c /^#define ISVISIBLE(/;" d file: +Key dwm.c /^} Key;$/;" t typeref:struct:__anon0f53a99e0908 file: +LDFLAGS config.mk /^LDFLAGS = ${LIBS}$/;" m +LENGTH dwm.c /^#define LENGTH(/;" d file: +LIBS config.mk /^LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lXrender -lImlib2$/;" m +Layout dwm.c /^} Layout;$/;" t typeref:struct:__anon0f53a99e0a08 file: +MANPREFIX config.mk /^MANPREFIX = ${PREFIX}\/share\/man$/;" m +MAX util.h /^#define MAX(/;" d +MIN util.h /^#define MIN(/;" d +MODKEY keys.h /^#define MODKEY /;" d +MOUSEMASK dwm.c /^#define MOUSEMASK /;" d file: +Manager dwm.c /^enum { Manager, Xembed, XembedInfo, XLast }; \/* Xembed atoms *\/$/;" e enum:__anon0f53a99e0403 file: +Monitor dwm.c /^struct Monitor {$/;" s file: +Monitor dwm.c /^typedef struct Monitor Monitor;$/;" t typeref:struct:Monitor file: +NAME dwm.1 /^.SH NAME$/;" s title:DWM +NetActiveWindow dwm.c /^ NetWMFullscreen, NetActiveWindow, NetWMWindowType,$/;" e enum:__anon0f53a99e0303 file: +NetClientList dwm.c /^ NetWMWindowTypeDialog, NetClientList, NetLast }; \/* EWMH atoms *\/$/;" e enum:__anon0f53a99e0303 file: +NetLast dwm.c /^ NetWMWindowTypeDialog, NetClientList, NetLast }; \/* EWMH atoms *\/$/;" e enum:__anon0f53a99e0303 file: +NetSupported dwm.c /^enum { NetSupported, NetWMName, NetWMIcon, NetWMState, NetWMCheck, $/;" e enum:__anon0f53a99e0303 file: +NetSystemTray dwm.c /^ NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayOrientationHorz,$/;" e enum:__anon0f53a99e0303 file: +NetSystemTrayOP dwm.c /^ NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayOrientationHorz,$/;" e enum:__anon0f53a99e0303 file: +NetSystemTrayOrientation dwm.c /^ NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayOrientationHorz,$/;" e enum:__anon0f53a99e0303 file: +NetSystemTrayOrientationHorz dwm.c /^ NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayOrientationHorz,$/;" e enum:__anon0f53a99e0303 file: +NetWMCheck dwm.c /^enum { NetSupported, NetWMName, NetWMIcon, NetWMState, NetWMCheck, $/;" e enum:__anon0f53a99e0303 file: +NetWMFullscreen dwm.c /^ NetWMFullscreen, NetActiveWindow, NetWMWindowType,$/;" e enum:__anon0f53a99e0303 file: +NetWMIcon dwm.c /^enum { NetSupported, NetWMName, NetWMIcon, NetWMState, NetWMCheck, $/;" e enum:__anon0f53a99e0303 file: +NetWMName dwm.c /^enum { NetSupported, NetWMName, NetWMIcon, NetWMState, NetWMCheck, $/;" e enum:__anon0f53a99e0303 file: +NetWMState dwm.c /^enum { NetSupported, NetWMName, NetWMIcon, NetWMState, NetWMCheck, $/;" e enum:__anon0f53a99e0303 file: +NetWMWindowType dwm.c /^ NetWMFullscreen, NetActiveWindow, NetWMWindowType,$/;" e enum:__anon0f53a99e0303 file: +NetWMWindowTypeDialog dwm.c /^ NetWMWindowTypeDialog, NetClientList, NetLast }; \/* EWMH atoms *\/$/;" e enum:__anon0f53a99e0303 file: +NumTags dwm.c /^struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; };$/;" s file: +OBJ Makefile /^OBJ = ${SRC:.c=.o}$/;" m +OPTIONS dwm.1 /^.SH OPTIONS$/;" s title:DWM +PREFIX config.mk /^PREFIX = \/usr\/local$/;" m +Pertag dwm.c /^struct Pertag {$/;" s file: +Pertag dwm.c /^typedef struct Pertag Pertag;$/;" t typeref:struct:Pertag file: +Rule dwm.c /^} Rule;$/;" t typeref:struct:__anon0f53a99e0b08 file: +SEE dwm.1 /^.SH SEE ALSO$/;" s title:DWM +SHCMD keys.h /^#define SHCMD(/;" d +SIGNALS dwm.1 /^.SH SIGNALS$/;" s title:DWM +SRC Makefile /^SRC = drw.c dwm.c util.c$/;" m +SYNOPSIS dwm.1 /^.SH SYNOPSIS$/;" s title:DWM +SYSTEM_TRAY_REQUEST_DOCK dwm.c /^#define SYSTEM_TRAY_REQUEST_DOCK /;" d file: +SchemeNorm dwm.c /^enum { SchemeNorm, SchemeSel, SchemeUrg, SchemeTitle }; \/* color schemes *\/$/;" e enum:__anon0f53a99e0203 file: +SchemeSel dwm.c /^enum { SchemeNorm, SchemeSel, SchemeUrg, SchemeTitle }; \/* color schemes *\/$/;" e enum:__anon0f53a99e0203 file: +SchemeTitle dwm.c /^enum { SchemeNorm, SchemeSel, SchemeUrg, SchemeTitle }; \/* color schemes *\/$/;" e enum:__anon0f53a99e0203 file: +SchemeUrg dwm.c /^enum { SchemeNorm, SchemeSel, SchemeUrg, SchemeTitle }; \/* color schemes *\/$/;" e enum:__anon0f53a99e0203 file: +Systray dwm.c /^struct Systray {$/;" s file: +Systray dwm.c /^typedef struct Systray Systray;$/;" t typeref:struct:Systray file: +TAGKEYS keys.h /^#define TAGKEYS(/;" d +TAGMASK dwm.c /^#define TAGMASK /;" d file: +TERMINAL config.def.h /^#define TERMINAL /;" d +TERMINAL config.h /^#define TERMINAL /;" d +TERMINAL keys.h /^#define TERMINAL /;" d +TEXTW dwm.c /^#define TEXTW(/;" d file: +USAGE dwm.1 /^.SH USAGE$/;" s title:DWM +UTF_INVALID drw.c /^#define UTF_INVALID /;" d file: +UTF_SIZ drw.c /^#define UTF_SIZ /;" d file: +VERSION config.mk /^VERSION = 6.5$/;" m +VERSION_MAJOR dwm.c /^#define VERSION_MAJOR /;" d file: +VERSION_MINOR dwm.c /^#define VERSION_MINOR /;" d file: +WIDTH dwm.c /^#define WIDTH(/;" d file: +WMDelete dwm.c /^enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; \/* default atoms *\/$/;" e enum:__anon0f53a99e0503 file: +WMLast dwm.c /^enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; \/* default atoms *\/$/;" e enum:__anon0f53a99e0503 file: +WMProtocols dwm.c /^enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; \/* default atoms *\/$/;" e enum:__anon0f53a99e0503 file: +WMState dwm.c /^enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; \/* default atoms *\/$/;" e enum:__anon0f53a99e0503 file: +WMTakeFocus dwm.c /^enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; \/* default atoms *\/$/;" e enum:__anon0f53a99e0503 file: +X11INC config.mk /^X11INC = \/usr\/X11R6\/include$/;" m +X11LIB config.mk /^X11LIB = \/usr\/X11R6\/lib$/;" m +XEMBED_EMBEDDED_NOTIFY dwm.c /^#define XEMBED_EMBEDDED_NOTIFY /;" d file: +XEMBED_EMBEDDED_VERSION dwm.c /^#define XEMBED_EMBEDDED_VERSION /;" d file: +XEMBED_FOCUS_IN dwm.c /^#define XEMBED_FOCUS_IN /;" d file: +XEMBED_MAPPED dwm.c /^#define XEMBED_MAPPED /;" d file: +XEMBED_MODALITY_ON dwm.c /^#define XEMBED_MODALITY_ON /;" d file: +XEMBED_WINDOW_ACTIVATE dwm.c /^#define XEMBED_WINDOW_ACTIVATE /;" d file: +XEMBED_WINDOW_DEACTIVATE dwm.c /^#define XEMBED_WINDOW_DEACTIVATE /;" d file: +XINERAMAFLAGS config.mk /^XINERAMAFLAGS = -DXINERAMA$/;" m +XINERAMALIBS config.mk /^XINERAMALIBS = -lXinerama$/;" m +XLast dwm.c /^enum { Manager, Xembed, XembedInfo, XLast }; \/* Xembed atoms *\/$/;" e enum:__anon0f53a99e0403 file: +Xembed dwm.c /^enum { Manager, Xembed, XembedInfo, XLast }; \/* Xembed atoms *\/$/;" e enum:__anon0f53a99e0403 file: +XembedInfo dwm.c /^enum { Manager, Xembed, XembedInfo, XLast }; \/* Xembed atoms *\/$/;" e enum:__anon0f53a99e0403 file: +__anon0f5116430103 drw.c /^ enum { nomatches_len = 64 };$/;" g function:drw_text file: +__anon0f5116430208 drw.c /^ static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;$/;" s function:drw_text file: +__anon0f5116480108 drw.h /^typedef struct {$/;" s +__anon0f5116480203 drw.h /^enum { ColFg, ColBg, ColBorder }; \/* Clr scheme index *\/$/;" g +__anon0f5116480308 drw.h /^typedef struct {$/;" s +__anon0f53a99e0103 dwm.c /^enum { CurNormal, CurResize, CurMove, CurLast }; \/* cursor *\/$/;" g file: +__anon0f53a99e0203 dwm.c /^enum { SchemeNorm, SchemeSel, SchemeUrg, SchemeTitle }; \/* color schemes *\/$/;" g file: +__anon0f53a99e0303 dwm.c /^enum { NetSupported, NetWMName, NetWMIcon, NetWMState, NetWMCheck, $/;" g file: +__anon0f53a99e0403 dwm.c /^enum { Manager, Xembed, XembedInfo, XLast }; \/* Xembed atoms *\/$/;" g file: +__anon0f53a99e0503 dwm.c /^enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; \/* default atoms *\/$/;" g file: +__anon0f53a99e0603 dwm.c /^enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,$/;" g file: +__anon0f53a99e070a dwm.c /^typedef union {$/;" u file: +__anon0f53a99e0808 dwm.c /^typedef struct {$/;" s file: +__anon0f53a99e0908 dwm.c /^typedef struct {$/;" s file: +__anon0f53a99e0a08 dwm.c /^typedef struct {$/;" s file: +__anon0f53a99e0b08 dwm.c /^typedef struct {$/;" s file: +all Makefile /^all: options dwm$/;" t +applyrules dwm.c /^applyrules(Client *c)$/;" f typeref:typename:void +applysizehints dwm.c /^applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact)$/;" f typeref:typename:int +arg dwm.c /^ const Arg arg;$/;" m struct:__anon0f53a99e0808 typeref:typename:const Arg file: +arg dwm.c /^ const Arg arg;$/;" m struct:__anon0f53a99e0908 typeref:typename:const Arg file: +arrange dwm.c /^ void (*arrange)(Monitor *);$/;" m struct:__anon0f53a99e0a08 typeref:typename:void (*)(Monitor *) file: +arrange dwm.c /^arrange(Monitor *m)$/;" f typeref:typename:void +arrangemon dwm.c /^arrangemon(Monitor *m)$/;" f typeref:typename:void +attach dwm.c /^attach(Client *c)$/;" f typeref:typename:void +attachstack dwm.c /^attachstack(Client *c)$/;" f typeref:typename:void +autostartblocksh dwm.c /^static const char autostartblocksh[] = "autostart_blocking.sh";$/;" v typeref:typename:const char[] file: +autostartsh dwm.c /^static const char autostartsh[] = "autostart.sh";$/;" v typeref:typename:const char[] file: +barwin dwm.c /^ Window barwin;$/;" m struct:Monitor typeref:typename:Window file: +baseh dwm.c /^ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;$/;" m struct:Client typeref:typename:int file: +basew dwm.c /^ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;$/;" m struct:Client typeref:typename:int file: +bh dwm.c /^static int bh; \/* bar height *\/$/;" v typeref:typename:int file: +borderpx config.def.h /^static const unsigned int borderpx = 2; \/* border pixel of windows *\/$/;" v typeref:typename:const unsigned int +borderpx config.h /^static const unsigned int borderpx = 2; \/* border pixel of windows *\/$/;" v typeref:typename:const unsigned int +broken dwm.c /^static const char broken[] = "broken";$/;" v typeref:typename:const char[] file: +button dwm.c /^ unsigned int button;$/;" m struct:__anon0f53a99e0808 typeref:typename:unsigned int file: +buttonpress dwm.c /^buttonpress(XEvent *e)$/;" f typeref:typename:void +buttons keys.h /^static const Button buttons[] = {$/;" v typeref:typename:const Button[] +bw dwm.c /^ int bw, oldbw;$/;" m struct:Client typeref:typename:int file: +by dwm.c /^ int by; \/* bar geometry *\/$/;" m struct:Monitor typeref:typename:int file: +chain dwm.c /^ KeySym chain;$/;" m struct:__anon0f53a99e0908 typeref:typename:KeySym file: +checkotherwm dwm.c /^checkotherwm(void)$/;" f typeref:typename:void +class dwm.c /^ const char *class;$/;" m struct:__anon0f53a99e0b08 typeref:typename:const char * file: +clean Makefile /^clean:$/;" t +cleanup dwm.c /^cleanup(void)$/;" f typeref:typename:void +cleanupmon dwm.c /^cleanupmon(Monitor *mon)$/;" f typeref:typename:void +click dwm.c /^ unsigned int click;$/;" m struct:__anon0f53a99e0808 typeref:typename:unsigned int file: +clientmessage dwm.c /^clientmessage(XEvent *e)$/;" f typeref:typename:void +clients dwm.c /^ Client *clients;$/;" m struct:Monitor typeref:typename:Client * file: +codepoint drw.c /^ static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;$/;" m struct:drw_text::__anon0f5116430208 typeref:typename:long[] file: +config.h Makefile /^config.h:$/;" t +configure dwm.c /^configure(Client *c)$/;" f typeref:typename:void +configurenotify dwm.c /^configurenotify(XEvent *e)$/;" f typeref:typename:void +configurerequest dwm.c /^configurerequest(XEvent *e)$/;" f typeref:typename:void +createmon dwm.c /^createmon(void)$/;" f typeref:typename:Monitor * +cursor drw.h /^ Cursor cursor;$/;" m struct:__anon0f5116480108 typeref:typename:Cursor +cursor dwm.c /^static Cur *cursor[CurLast];$/;" v typeref:typename:Cur * [] file: +curtag dwm.c /^ unsigned int curtag, prevtag; \/* current and previous tag *\/$/;" m struct:Pertag typeref:typename:unsigned int file: +dec_light keys.h /^static const char *dec_light[] = { "brightnessctl", "set", "10%-", NULL };$/;" v typeref:typename:const char * [] +destroynotify dwm.c /^destroynotify(XEvent *e)$/;" f typeref:typename:void +detach dwm.c /^detach(Client *c)$/;" f typeref:typename:void +detachstack dwm.c /^detachstack(Client *c)$/;" f typeref:typename:void +die util.c /^die(const char *fmt, ...)$/;" f typeref:typename:void +dirtomon dwm.c /^dirtomon(int dir)$/;" f typeref:typename:Monitor * +dist Makefile /^dist: clean$/;" t +dmenucmd keys.h /^static const char *dmenucmd[] = { "dmenu_run", "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gr/;" v typeref:typename:const char * [] +dmenufont keys.h /^static const char dmenufont[] = "monospace:size=10";$/;" v typeref:typename:const char[] +dpy drw.h /^ Display *dpy;$/;" m struct:Fnt typeref:typename:Display * +dpy drw.h /^ Display *dpy;$/;" m struct:__anon0f5116480308 typeref:typename:Display * +dpy dwm.c /^static Display *dpy;$/;" v typeref:typename:Display * file: +drawable drw.h /^ Drawable drawable;$/;" m struct:__anon0f5116480308 typeref:typename:Drawable +drawbar dwm.c /^drawbar(Monitor *m)$/;" f typeref:typename:void +drawbars dwm.c /^drawbars(void)$/;" f typeref:typename:void +drawstatusbar dwm.c /^drawstatusbar(Monitor *m, int bh, char* stext) {$/;" f typeref:typename:int +drw dwm.c /^static Drw *drw;$/;" v typeref:typename:Drw * file: +drw_clr_create drw.c /^drw_clr_create(Drw *drw, Clr *dest, const char *clrname)$/;" f typeref:typename:void +drw_create drw.c /^drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)$/;" f typeref:typename:Drw * +drw_cur_create drw.c /^drw_cur_create(Drw *drw, int shape)$/;" f typeref:typename:Cur * +drw_cur_free drw.c /^drw_cur_free(Drw *drw, Cur *cursor)$/;" f typeref:typename:void +drw_font_getexts drw.c /^drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h/;" f typeref:typename:void +drw_fontset_create drw.c /^drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)$/;" f typeref:typename:Fnt * +drw_fontset_free drw.c /^drw_fontset_free(Fnt *font)$/;" f typeref:typename:void +drw_fontset_getwidth drw.c /^drw_fontset_getwidth(Drw *drw, const char *text)$/;" f typeref:typename:unsigned int +drw_fontset_getwidth_clamp drw.c /^drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n)$/;" f typeref:typename:unsigned int +drw_free drw.c /^drw_free(Drw *drw)$/;" f typeref:typename:void +drw_map drw.c /^drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)$/;" f typeref:typename:void +drw_pic drw.c /^drw_pic(Drw *drw, int x, int y, unsigned int w, unsigned int h, Picture pic)$/;" f typeref:typename:void +drw_picture_create_resized drw.c /^drw_picture_create_resized(Drw *drw, char *src, unsigned int srcw, unsigned int srch, unsigned i/;" f typeref:typename:Picture +drw_rect drw.c /^drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)$/;" f typeref:typename:void +drw_resize drw.c /^drw_resize(Drw *drw, unsigned int w, unsigned int h)$/;" f typeref:typename:void +drw_scm_create drw.c /^drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)$/;" f typeref:typename:Clr * +drw_setfontset drw.c /^drw_setfontset(Drw *drw, Fnt *set)$/;" f typeref:typename:void +drw_setscheme drw.c /^drw_setscheme(Drw *drw, Clr *scm)$/;" f typeref:typename:void +drw_text drw.c /^drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char */;" f typeref:typename:int +dwindle fibonacci.c /^dwindle(Monitor *mon) {$/;" f typeref:typename:void +dwm Makefile /^dwm: ${OBJ}$/;" t +dwmdir dwm.c /^static const char dwmdir[] = "dwm";$/;" v typeref:typename:const char[] file: +ecalloc util.c /^ecalloc(size_t nmemb, size_t size)$/;" f typeref:typename:void * +enternotify dwm.c /^enternotify(XEvent *e)$/;" f typeref:typename:void +expose dwm.c /^expose(XEvent *e)$/;" f typeref:typename:void +f dwm.c /^ float f;$/;" m union:__anon0f53a99e070a typeref:typename:float file: +fibonacci fibonacci.c /^fibonacci(Monitor *mon, int s) {$/;" f typeref:typename:void +focus dwm.c /^focus(Client *c)$/;" f typeref:typename:void +focusin dwm.c /^focusin(XEvent *e)$/;" f typeref:typename:void +focusmon dwm.c /^focusmon(const Arg *arg)$/;" f typeref:typename:void +focusstack dwm.c /^focusstack(const Arg *arg)$/;" f typeref:typename:void +fonts config.def.h /^static const char *fonts[] = { "monospace:size=9", "DroidSansMono Nerd Font:size=8", "N/;" v typeref:typename:const char * [] +fonts config.h /^static const char *fonts[] = { "monospace:size=9", "DroidSansMono Nerd Font:size=8", "N/;" v typeref:typename:const char * [] +fonts drw.h /^ Fnt *fonts;$/;" m struct:__anon0f5116480308 typeref:typename:Fnt * +freeicon dwm.c /^freeicon(Client *c)$/;" f typeref:typename:void +func dwm.c /^ void (*func)(const Arg *);$/;" m struct:__anon0f53a99e0908 typeref:typename:void (*)(const Arg *) file: +func dwm.c /^ void (*func)(const Arg *arg);$/;" m struct:__anon0f53a99e0808 typeref:typename:void (*)(const Arg * arg) file: +gappx config.def.h /^static const unsigned int gappx = 10; \/* gaps between windows *\/$/;" v typeref:typename:const unsigned int +gappx config.h /^static const unsigned int gappx = 10; \/* gaps between windows *\/$/;" v typeref:typename:const unsigned int +gappx dwm.c /^ int gappx; \/* gaps between windows *\/$/;" m struct:Monitor typeref:typename:int file: +gc drw.h /^ GC gc;$/;" m struct:__anon0f5116480308 typeref:typename:GC +getatomprop dwm.c /^getatomprop(Client *c, Atom prop)$/;" f typeref:typename:Atom +geticonprop dwm.c /^geticonprop(Window win, unsigned int *picw, unsigned int *pich)$/;" f typeref:typename:Picture +getrootptr dwm.c /^getrootptr(int *x, int *y)$/;" f typeref:typename:int +getstate dwm.c /^getstate(Window w)$/;" f typeref:typename:long +getsystraywidth dwm.c /^getsystraywidth()$/;" f typeref:typename:unsigned int +gettextprop dwm.c /^gettextprop(Window w, Atom atom, char *text, unsigned int size)$/;" f typeref:typename:int +grabbuttons dwm.c /^grabbuttons(Client *c, int focused)$/;" f typeref:typename:void +grabkeys dwm.c /^grabkeys(void)$/;" f typeref:typename:void +h drw.h /^ unsigned int h;$/;" m struct:Fnt typeref:typename:unsigned int +h drw.h /^ unsigned int w, h;$/;" m struct:__anon0f5116480308 typeref:typename:unsigned int +h dwm.c /^ int x, y, w, h;$/;" m struct:Client typeref:typename:int file: +handler dwm.c /^static void (*handler[LASTEvent]) (XEvent *) = {$/;" v typeref:typename:void (* [LASTEvent])(XEvent *) file: +hintsvalid dwm.c /^ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;$/;" m struct:Client typeref:typename:int file: +i dwm.c /^ int i;$/;" m union:__anon0f53a99e070a typeref:typename:int file: +ich dwm.c /^ unsigned int icw, ich; Picture icon;$/;" m struct:Client typeref:typename:unsigned int file: +icon dwm.c /^ unsigned int icw, ich; Picture icon;$/;" m struct:Client typeref:typename:Picture file: +icons dwm.c /^ Client *icons;$/;" m struct:Systray typeref:typename:Client * file: +icw dwm.c /^ unsigned int icw, ich; Picture icon;$/;" m struct:Client typeref:typename:unsigned int file: +idx drw.c /^ static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;$/;" m struct:drw_text::__anon0f5116430208 typeref:typename:unsigned int file: +inc_light keys.h /^static const char *inc_light[] = { "brightnessctl", "set", "+10%", NULL };$/;" v typeref:typename:const char * [] +inch dwm.c /^ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;$/;" m struct:Client typeref:typename:int file: +incnmaster dwm.c /^incnmaster(const Arg *arg)$/;" f typeref:typename:void +incw dwm.c /^ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;$/;" m struct:Client typeref:typename:int file: +install Makefile /^install: all$/;" t +instance dwm.c /^ const char *instance;$/;" m struct:__anon0f53a99e0b08 typeref:typename:const char * file: +isfixed dwm.c /^ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, CenterThisWindow;$/;" m struct:Client typeref:typename:int file: +isfloating dwm.c /^ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, CenterThisWindow;$/;" m struct:Client typeref:typename:int file: +isfloating dwm.c /^ int isfloating;$/;" m struct:__anon0f53a99e0b08 typeref:typename:int file: +isfullscreen dwm.c /^ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, CenterThisWindow;$/;" m struct:Client typeref:typename:int file: +isuniquegeom dwm.c /^isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info)$/;" f typeref:typename:int file: +isurgent dwm.c /^ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, CenterThisWindow;$/;" m struct:Client typeref:typename:int file: +keychain dwm.c /^static KeySym keychain = -1;$/;" v typeref:typename:KeySym file: +keypress dwm.c /^keypress(XEvent *e)$/;" f typeref:typename:void +keys keys.h /^static const Key keys[] = {$/;" v typeref:typename:const Key[] +keysym dwm.c /^ KeySym keysym;$/;" m struct:__anon0f53a99e0908 typeref:typename:KeySym file: +killclient dwm.c /^killclient(const Arg *arg)$/;" f typeref:typename:void +layouts layouts.h /^static const Layout layouts[] = {$/;" v typeref:typename:const Layout[] +limitexceeded dwm.c /^struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; };$/;" m struct:NumTags typeref:typename:char[] file: +localshare dwm.c /^static const char localshare[] = ".local\/share";$/;" v typeref:typename:const char[] file: +lockfullscreen layouts.h /^static const int lockfullscreen = 0; \/* 1 will force focus on the fullscreen window *\/$/;" v typeref:typename:const int +lrpad dwm.c /^static int lrpad; \/* sum of left and right padding for text *\/$/;" v typeref:typename:int file: +lt dwm.c /^ const Layout *lt[2];$/;" m struct:Monitor typeref:typename:const Layout * [2] file: +ltidxs dwm.c /^ const Layout *ltidxs[LENGTH(tags) + 1][2]; \/* matrix of tags and layouts indexes *\/$/;" m struct:Pertag typeref:typename:const Layout * [][2] file: +ltsymbol dwm.c /^ char ltsymbol[16];$/;" m struct:Monitor typeref:typename:char[16] file: +main dwm.c /^main(int argc, char *argv[])$/;" f typeref:typename:int +main transient.c /^int main(void) {$/;" f typeref:typename:int +manage dwm.c /^manage(Window w, XWindowAttributes *wa)$/;" f typeref:typename:void +mappingnotify dwm.c /^mappingnotify(XEvent *e)$/;" f typeref:typename:void +maprequest dwm.c /^maprequest(XEvent *e)$/;" f typeref:typename:void +mask dwm.c /^ unsigned int mask;$/;" m struct:__anon0f53a99e0808 typeref:typename:unsigned int file: +maxa dwm.c /^ float mina, maxa;$/;" m struct:Client typeref:typename:float file: +maxh dwm.c /^ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;$/;" m struct:Client typeref:typename:int file: +maxw dwm.c /^ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;$/;" m struct:Client typeref:typename:int file: +mfact dwm.c /^ float mfact;$/;" m struct:Monitor typeref:typename:float file: +mfact layouts.h /^static const float mfact = 0.55; \/* factor of master area size [0.05..0.95] *\/$/;" v typeref:typename:const float +mfacts dwm.c /^ float mfacts[LENGTH(tags) + 1]; \/* mfacts per tag *\/$/;" m struct:Pertag typeref:typename:float[] file: +mh dwm.c /^ int mx, my, mw, mh; \/* screen size *\/$/;" m struct:Monitor typeref:typename:int file: +mina dwm.c /^ float mina, maxa;$/;" m struct:Client typeref:typename:float file: +minh dwm.c /^ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;$/;" m struct:Client typeref:typename:int file: +minw dwm.c /^ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;$/;" m struct:Client typeref:typename:int file: +mod dwm.c /^ unsigned int mod;$/;" m struct:__anon0f53a99e0908 typeref:typename:unsigned int file: +mon dwm.c /^ Monitor *mon;$/;" m struct:Client typeref:typename:Monitor * file: +monitor dwm.c /^ int monitor;$/;" m struct:__anon0f53a99e0b08 typeref:typename:int file: +monocle dwm.c /^monocle(Monitor *m)$/;" f typeref:typename:void +mons dwm.c /^static Monitor *mons, *selmon;$/;" v typeref:typename:Monitor * file: +motionnotify dwm.c /^motionnotify(XEvent *e)$/;" f typeref:typename:void +movemouse dwm.c /^movemouse(const Arg *arg)$/;" f typeref:typename:void +movestack movestack.c /^movestack(const Arg *arg) {$/;" f typeref:typename:void +mw dwm.c /^ int mx, my, mw, mh; \/* screen size *\/$/;" m struct:Monitor typeref:typename:int file: +mx dwm.c /^ int mx, my, mw, mh; \/* screen size *\/$/;" m struct:Monitor typeref:typename:int file: +my dwm.c /^ int mx, my, mw, mh; \/* screen size *\/$/;" m struct:Monitor typeref:typename:int file: +name dwm.c /^ char name[256];$/;" m struct:Client typeref:typename:char[256] file: +netatom dwm.c /^static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast];$/;" v typeref:typename:Atom[] file: +neverfocus dwm.c /^ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, CenterThisWindow;$/;" m struct:Client typeref:typename:int file: +next drw.h /^ struct Fnt *next;$/;" m struct:Fnt typeref:struct:Fnt * +next dwm.c /^ Client *next;$/;" m struct:Client typeref:typename:Client * file: +next dwm.c /^ Monitor *next;$/;" m struct:Monitor typeref:typename:Monitor * file: +nexttiled dwm.c /^nexttiled(Client *c)$/;" f typeref:typename:Client * +nmaster dwm.c /^ int nmaster;$/;" m struct:Monitor typeref:typename:int file: +nmaster layouts.h /^static const int nmaster = 1; \/* number of clients in master area *\/$/;" v typeref:typename:const int +nmasters dwm.c /^ int nmasters[LENGTH(tags) + 1]; \/* number of windows in master area *\/$/;" m struct:Pertag typeref:typename:int[] file: +nomatches_len drw.c /^ enum { nomatches_len = 64 };$/;" e enum:drw_text::__anon0f5116430103 file: +num dwm.c /^ int num;$/;" m struct:Monitor typeref:typename:int file: +numlockmask dwm.c /^static unsigned int numlockmask = 0;$/;" v typeref:typename:unsigned int file: +nvimcmd keys.h /^static const char *nvimcmd[] = { TERMINAL, "-e", "nvim", "KleinWiki\/index.md", NULL };$/;" v typeref:typename:const char * [] +oldbw dwm.c /^ int bw, oldbw;$/;" m struct:Client typeref:typename:int file: +oldh dwm.c /^ int oldx, oldy, oldw, oldh;$/;" m struct:Client typeref:typename:int file: +oldstate dwm.c /^ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, CenterThisWindow;$/;" m struct:Client typeref:typename:int file: +oldw dwm.c /^ int oldx, oldy, oldw, oldh;$/;" m struct:Client typeref:typename:int file: +oldx dwm.c /^ int oldx, oldy, oldw, oldh;$/;" m struct:Client typeref:typename:int file: +oldy dwm.c /^ int oldx, oldy, oldw, oldh;$/;" m struct:Client typeref:typename:int file: +options Makefile /^options:$/;" t +pattern drw.h /^ FcPattern *pattern;$/;" m struct:Fnt typeref:typename:FcPattern * +pertag dwm.c /^ Pertag *pertag;$/;" m struct:Monitor typeref:typename:Pertag * file: +picture drw.h /^ Picture picture;$/;" m struct:__anon0f5116480308 typeref:typename:Picture +pop dwm.c /^pop(Client *c)$/;" f typeref:typename:void +prealpha dwm.c /^static uint32_t prealpha(uint32_t p) {$/;" f typeref:typename:uint32_t file: +prevtag dwm.c /^ unsigned int curtag, prevtag; \/* current and previous tag *\/$/;" m struct:Pertag typeref:typename:unsigned int file: +propertynotify dwm.c /^propertynotify(XEvent *e)$/;" f typeref:typename:void +quit dwm.c /^quit(const Arg *arg)$/;" f typeref:typename:void +recttomon dwm.c /^recttomon(int x, int y, int w, int h)$/;" f typeref:typename:Monitor * +removesystrayicon dwm.c /^removesystrayicon(Client *i)$/;" f typeref:typename:void +resize dwm.c /^resize(Client *c, int x, int y, int w, int h, int interact)$/;" f typeref:typename:void +resizebarwin dwm.c /^resizebarwin(Monitor *m) {$/;" f typeref:typename:void +resizeclient dwm.c /^resizeclient(Client *c, int x, int y, int w, int h)$/;" f typeref:typename:void +resizehints layouts.h /^static const int resizehints = 1; \/* 1 means respect size hints in tiled resizals *\/$/;" v typeref:typename:const int +resizemouse dwm.c /^resizemouse(const Arg *arg)$/;" f typeref:typename:void +resizerequest dwm.c /^resizerequest(XEvent *e)$/;" f typeref:typename:void +restack dwm.c /^restack(Monitor *m)$/;" f typeref:typename:void +restart dwm.c /^static int restart = 0;$/;" v typeref:typename:int file: +root drw.h /^ Window root;$/;" m struct:__anon0f5116480308 typeref:typename:Window +root dwm.c /^static Window root, wmcheckwin;$/;" v typeref:typename:Window file: +rules config.def.h /^static const Rule rules[] = {$/;" v typeref:typename:const Rule[] +rules config.h /^static const Rule rules[] = {$/;" v typeref:typename:const Rule[] +run dwm.c /^run(void)$/;" f typeref:typename:void +runautostart dwm.c /^runautostart(void)$/;" f typeref:typename:void +running dwm.c /^static int running = 1;$/;" v typeref:typename:int file: +scan dwm.c /^scan(void)$/;" f typeref:typename:void +scheme drw.h /^ Clr *scheme;$/;" m struct:__anon0f5116480308 typeref:typename:Clr * +scheme dwm.c /^static Clr **scheme;$/;" v typeref:typename:Clr ** file: +screen drw.h /^ int screen;$/;" m struct:__anon0f5116480308 typeref:typename:int +screen dwm.c /^static int screen;$/;" v typeref:typename:int file: +scrotselcmd keys.h /^static const char *scrotselcmd[] = { "scrot", "-s", "\/home\/klein\/Pictures\/screenshots\/Scree/;" v typeref:typename:const char * [] +sel dwm.c /^ Client *sel;$/;" m struct:Monitor typeref:typename:Client * file: +sellt dwm.c /^ unsigned int sellt;$/;" m struct:Monitor typeref:typename:unsigned int file: +sellts dwm.c /^ unsigned int sellts[LENGTH(tags) + 1]; \/* selected layouts *\/$/;" m struct:Pertag typeref:typename:unsigned int[] file: +selmon dwm.c /^static Monitor *mons, *selmon;$/;" v typeref:typename:Monitor * file: +seltags dwm.c /^ unsigned int seltags;$/;" m struct:Monitor typeref:typename:unsigned int file: +sendevent dwm.c /^sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3, long d4)$/;" f typeref:typename:int +sendmon dwm.c /^sendmon(Client *c, Monitor *m)$/;" f typeref:typename:void +setclientstate dwm.c /^setclientstate(Client *c, long state)$/;" f typeref:typename:void +setfocus dwm.c /^setfocus(Client *c)$/;" f typeref:typename:void +setfullscreen dwm.c /^setfullscreen(Client *c, int fullscreen)$/;" f typeref:typename:void +setgaps dwm.c /^setgaps(const Arg *arg)$/;" f typeref:typename:void +setlayout dwm.c /^setlayout(const Arg *arg)$/;" f typeref:typename:void +setmfact dwm.c /^setmfact(const Arg *arg)$/;" f typeref:typename:void +setup dwm.c /^setup(void)$/;" f typeref:typename:void +seturgent dwm.c /^seturgent(Client *c, int urg)$/;" f typeref:typename:void +sh dwm.c /^static int sw, sh; \/* X display screen geometry width, height *\/$/;" v typeref:typename:int file: +showbar config.def.h /^static const int showbar = 1; \/* 0 means no bar *\/$/;" v typeref:typename:const int +showbar config.h /^static const int showbar = 1; \/* 0 means no bar *\/$/;" v typeref:typename:const int +showbar dwm.c /^ int showbar;$/;" m struct:Monitor typeref:typename:int file: +showbars dwm.c /^ int showbars[LENGTH(tags) + 1]; \/* display bar for the current tag *\/$/;" m struct:Pertag typeref:typename:int[] file: +showhide dwm.c /^showhide(Client *c)$/;" f typeref:typename:void +showsystray config.def.h /^static const int showsystray = 1; \/* 0 means no systray *\/$/;" v typeref:typename:const int +showsystray config.h /^static const int showsystray = 1; \/* 0 means no systray *\/$/;" v typeref:typename:const int +sigchld dwm.c /^sigchld(int unused)$/;" f typeref:typename:void +sighup dwm.c /^sighup(int unused)$/;" f typeref:typename:void +sigterm dwm.c /^sigterm(int unused)$/;" f typeref:typename:void +snap config.def.h /^static const unsigned int snap = 32; \/* snap pixel *\/$/;" v typeref:typename:const unsigned int +snap config.h /^static const unsigned int snap = 32; \/* snap pixel *\/$/;" v typeref:typename:const unsigned int +snext dwm.c /^ Client *snext;$/;" m struct:Client typeref:typename:Client * file: +spawn dwm.c /^spawn(const Arg *arg)$/;" f typeref:typename:void +spiral fibonacci.c /^spiral(Monitor *mon) {$/;" f typeref:typename:void +stack dwm.c /^ Client *stack;$/;" m struct:Monitor typeref:typename:Client * file: +stext dwm.c /^static char stext[1024];$/;" v typeref:typename:char[1024] file: +sw dwm.c /^static int sw, sh; \/* X display screen geometry width, height *\/$/;" v typeref:typename:int file: +symbol dwm.c /^ const char *symbol;$/;" m struct:__anon0f53a99e0a08 typeref:typename:const char * file: +systray dwm.c /^static Systray *systray = NULL;$/;" v typeref:typename:Systray * file: +systrayonleft config.def.h /^static const unsigned int systrayonleft = 0; \/* 0: systray in the right corner, >0: systray /;" v typeref:typename:const unsigned int +systrayonleft config.h /^static const unsigned int systrayonleft = 0; \/* 0: systray in the right corner, >0: systray /;" v typeref:typename:const unsigned int +systraypinning config.def.h /^static const unsigned int systraypinning = 0; \/* 0: sloppy systray follows selected monitor, /;" v typeref:typename:const unsigned int +systraypinning config.h /^static const unsigned int systraypinning = 0; \/* 0: sloppy systray follows selected monitor, /;" v typeref:typename:const unsigned int +systraypinningfailfirst config.def.h /^static const int systraypinningfailfirst = 1; \/* 1: if pinning fails, display systray on the /;" v typeref:typename:const int +systraypinningfailfirst config.h /^static const int systraypinningfailfirst = 1; \/* 1: if pinning fails, display systray on the /;" v typeref:typename:const int +systrayspacing config.def.h /^static const unsigned int systrayspacing = 2; \/* systray spacing *\/$/;" v typeref:typename:const unsigned int +systrayspacing config.h /^static const unsigned int systrayspacing = 2; \/* systray spacing *\/$/;" v typeref:typename:const unsigned int +systraytomon dwm.c /^systraytomon(Monitor *m) {$/;" f typeref:typename:Monitor * +tag dwm.c /^tag(const Arg *arg)$/;" f typeref:typename:void +tagmon dwm.c /^tagmon(const Arg *arg)$/;" f typeref:typename:void +tags config.def.h /^static const char *tags[] = { " \\uf17c ", " \\uf1d8 ", " \\uf0ac ", " \\uf109 ", " \\uf09b ", "/;" v typeref:typename:const char * [] +tags config.h /^static const char *tags[] = { " \\uf17c ", " \\uf1d8 ", " \\uf0ac ", " \\uf109 ", " \\uf09b ", "/;" v typeref:typename:const char * [] +tags dwm.c /^ unsigned int tags;$/;" m struct:Client typeref:typename:unsigned int file: +tags dwm.c /^ unsigned int tags;$/;" m struct:__anon0f53a99e0b08 typeref:typename:unsigned int file: +tagscheme dwm.c /^static Clr **tagscheme;$/;" v typeref:typename:Clr ** file: +tagset dwm.c /^ unsigned int tagset[2];$/;" m struct:Monitor typeref:typename:unsigned int[2] file: +termcmd keys.h /^static const char *termcmd[] = { TERMINAL, NULL };$/;" v typeref:typename:const char * [] +tile dwm.c /^tile(Monitor *m)$/;" f typeref:typename:void +title dwm.c /^ const char *title;$/;" m struct:__anon0f53a99e0b08 typeref:typename:const char * file: +togglebar dwm.c /^togglebar(const Arg *arg)$/;" f typeref:typename:void +togglefloating dwm.c /^togglefloating(const Arg *arg)$/;" f typeref:typename:void +toggletag dwm.c /^toggletag(const Arg *arg)$/;" f typeref:typename:void +toggleview dwm.c /^toggleview(const Arg *arg)$/;" f typeref:typename:void +topbar config.def.h /^static const int topbar = 1; \/* 0 means bottom bar *\/$/;" v typeref:typename:const int +topbar config.h /^static const int topbar = 1; \/* 0 means bottom bar *\/$/;" v typeref:typename:const int +topbar dwm.c /^ int topbar;$/;" m struct:Monitor typeref:typename:int file: +ui dwm.c /^ unsigned int ui;$/;" m union:__anon0f53a99e070a typeref:typename:unsigned int file: +unfocus dwm.c /^unfocus(Client *c, int setfocus)$/;" f typeref:typename:void +uninstall Makefile /^uninstall:$/;" t +unmanage dwm.c /^unmanage(Client *c, int destroyed)$/;" f typeref:typename:void +unmapnotify dwm.c /^unmapnotify(XEvent *e)$/;" f typeref:typename:void +updatebarpos dwm.c /^updatebarpos(Monitor *m)$/;" f typeref:typename:void +updatebars dwm.c /^updatebars(void)$/;" f typeref:typename:void +updateclientlist dwm.c /^updateclientlist()$/;" f typeref:typename:void +updategeom dwm.c /^updategeom(void)$/;" f typeref:typename:int +updateicon dwm.c /^updateicon(Client *c)$/;" f typeref:typename:void +updatenumlockmask dwm.c /^updatenumlockmask(void)$/;" f typeref:typename:void +updatesizehints dwm.c /^updatesizehints(Client *c)$/;" f typeref:typename:void +updatestatus dwm.c /^updatestatus(void)$/;" f typeref:typename:void +updatesystray dwm.c /^updatesystray(void)$/;" f typeref:typename:void +updatesystrayicongeom dwm.c /^updatesystrayicongeom(Client *i, int w, int h)$/;" f typeref:typename:void +updatesystrayiconstate dwm.c /^updatesystrayiconstate(Client *i, XPropertyEvent *ev)$/;" f typeref:typename:void +updatetitle dwm.c /^updatetitle(Client *c)$/;" f typeref:typename:void +updatewindowtype dwm.c /^updatewindowtype(Client *c)$/;" f typeref:typename:void +updatewmhints dwm.c /^updatewmhints(Client *c)$/;" f typeref:typename:void +user_bh config.def.h /^static const int user_bh = 8; \/* 2 is the default spacing around the bar's fo/;" v typeref:typename:const int +user_bh config.h /^static const int user_bh = 8; \/* 2 is the default spacing around the bar's fo/;" v typeref:typename:const int +utf8decode drw.c /^utf8decode(const char *c, long *u, size_t clen)$/;" f typeref:typename:size_t file: +utf8decodebyte drw.c /^utf8decodebyte(const char c, size_t *i)$/;" f typeref:typename:long file: +utf8validate drw.c /^utf8validate(long *u, size_t i)$/;" f typeref:typename:size_t file: +utfbyte drw.c /^static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};$/;" v typeref:typename:const unsigned char[] file: +utfmask drw.c /^static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};$/;" v typeref:typename:const unsigned char[] file: +utfmax drw.c /^static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};$/;" v typeref:typename:const long[] file: +utfmin drw.c /^static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};$/;" v typeref:typename:const long[] file: +v dwm.c /^ const void *v;$/;" m union:__anon0f53a99e070a typeref:typename:const void * file: +view dwm.c /^view(const Arg *arg)$/;" f typeref:typename:void +w drw.h /^ unsigned int w, h;$/;" m struct:__anon0f5116480308 typeref:typename:unsigned int +w dwm.c /^ int x, y, w, h;$/;" m struct:Client typeref:typename:int file: +wh dwm.c /^ int wx, wy, ww, wh; \/* window area *\/$/;" m struct:Monitor typeref:typename:int file: +win dwm.c /^ Window win;$/;" m struct:Client typeref:typename:Window file: +win dwm.c /^ Window win;$/;" m struct:Systray typeref:typename:Window file: +wintoclient dwm.c /^wintoclient(Window w)$/;" f typeref:typename:Client * +wintomon dwm.c /^wintomon(Window w)$/;" f typeref:typename:Monitor * +wintosystrayicon dwm.c /^wintosystrayicon(Window w) {$/;" f typeref:typename:Client * +wmatom dwm.c /^static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast];$/;" v typeref:typename:Atom[] file: +wmcheckwin dwm.c /^static Window root, wmcheckwin;$/;" v typeref:typename:Window file: +ww dwm.c /^ int wx, wy, ww, wh; \/* window area *\/$/;" m struct:Monitor typeref:typename:int file: +wx dwm.c /^ int wx, wy, ww, wh; \/* window area *\/$/;" m struct:Monitor typeref:typename:int file: +wy dwm.c /^ int wx, wy, ww, wh; \/* window area *\/$/;" m struct:Monitor typeref:typename:int file: +x dwm.c /^ int x, y, w, h;$/;" m struct:Client typeref:typename:int file: +xatom dwm.c /^static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast];$/;" v typeref:typename:Atom[] file: +xerror dwm.c /^xerror(Display *dpy, XErrorEvent *ee)$/;" f typeref:typename:int +xerrordummy dwm.c /^xerrordummy(Display *dpy, XErrorEvent *ee)$/;" f typeref:typename:int +xerrorstart dwm.c /^xerrorstart(Display *dpy, XErrorEvent *ee)$/;" f typeref:typename:int +xerrorxlib dwm.c /^static int (*xerrorxlib)(Display *, XErrorEvent *);$/;" v typeref:typename:int (*)(Display *,XErrorEvent *) file: +xfont drw.h /^ XftFont *xfont;$/;" m struct:Fnt typeref:typename:XftFont * +xfont_create drw.c /^xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)$/;" f typeref:typename:Fnt * file: +xfont_free drw.c /^xfont_free(Fnt *font)$/;" f typeref:typename:void file: +y dwm.c /^ int x, y, w, h;$/;" m struct:Client typeref:typename:int file: +zoom dwm.c /^zoom(const Arg *arg)$/;" f typeref:typename:void diff --git a/KleinDwm/source/transient.c b/KleinDwm/source/transient.c new file mode 100644 index 0000000..040adb5 --- /dev/null +++ b/KleinDwm/source/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/KleinDwm/source/util.c b/KleinDwm/source/util.c new file mode 100644 index 0000000..648dee7 --- /dev/null +++ b/KleinDwm/source/util.c @@ -0,0 +1,171 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#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; +} + +int +normalizepath(const char *path, char **normal) +{ + size_t len = strlen(path); + *normal = (char *)malloc((len + 1) * sizeof(char)); + const char *walk = path; + const char *match; + size_t newlen = 0; + + while ((match = strchr(walk, '/'))) { + // Copy everything between match and walk + strncpy(*normal + newlen, walk, match - walk); + newlen += match - walk; + walk += match - walk; + + // Skip all repeating slashes + while (*walk == '/') + walk++; + + // If not last character in path + if (walk != path + len) + (*normal)[newlen++] = '/'; + } + + (*normal)[newlen++] = '\0'; + + // Copy remaining path + strcat(*normal, walk); + newlen += strlen(walk); + + *normal = (char *)realloc(*normal, newlen * sizeof(char)); + + return 0; +} + +int +parentdir(const char *path, char **parent) +{ + char *normal; + char *walk; + + normalizepath(path, &normal); + + // Pointer to last '/' + if (!(walk = strrchr(normal, '/'))) { + free(normal); + return -1; + } + + // Get path up to last '/' + size_t len = walk - normal; + *parent = (char *)malloc((len + 1) * sizeof(char)); + + // Copy path up to last '/' + strncpy(*parent, normal, len); + // Add null char + (*parent)[len] = '\0'; + + free(normal); + + return 0; +} + +int +mkdirp(const char *path) +{ + char *normal; + char *walk; + size_t normallen; + + normalizepath(path, &normal); + normallen = strlen(normal); + walk = normal; + + while (walk < normal + normallen + 1) { + // Get length from walk to next / + size_t n = strcspn(walk, "/"); + + // Skip path / + if (n == 0) { + walk++; + continue; + } + + // Length of current path segment + size_t curpathlen = walk - normal + n; + char curpath[curpathlen + 1]; + struct stat s; + + // Copy path segment to stat + strncpy(curpath, normal, curpathlen); + strcpy(curpath + curpathlen, ""); + int res = stat(curpath, &s); + + if (res < 0) { + if (errno == ENOENT) { + DEBUG("Making directory %s\n", curpath); + if (mkdir(curpath, 0700) < 0) { + fprintf(stderr, "Failed to make directory %s\n", curpath); + perror(""); + free(normal); + return -1; + } + } else { + fprintf(stderr, "Error statting directory %s\n", curpath); + perror(""); + free(normal); + return -1; + } + } + + // Continue to next path segment + walk += n; + } + + free(normal); + + return 0; +} + +int +nullterminate(char **str, size_t *len) +{ + if ((*str)[*len - 1] == '\0') + return 0; + + (*len)++; + *str = (char*)realloc(*str, *len * sizeof(char)); + (*str)[*len - 1] = '\0'; + + return 0; +} diff --git a/KleinDwm/source/util.c.orig b/KleinDwm/source/util.c.orig new file mode 100644 index 0000000..96b82c9 --- /dev/null +++ b/KleinDwm/source/util.c.orig @@ -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/KleinDwm/source/util.h b/KleinDwm/source/util.h new file mode 100644 index 0000000..73a238e --- /dev/null +++ b/KleinDwm/source/util.h @@ -0,0 +1,18 @@ +/* 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)) + +#ifdef _DEBUG +#define DEBUG(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG(...) +#endif + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); +int normalizepath(const char *path, char **normal); +int mkdirp(const char *path); +int parentdir(const char *path, char **parent); +int nullterminate(char **str, size_t *len); diff --git a/KleinDwm/source/util.o b/KleinDwm/source/util.o new file mode 100644 index 0000000000000000000000000000000000000000..d16536d7805540cedcb60212ce2d2d98b6f417fe GIT binary patch literal 4488 zcmb<-^>JfjWMqH=Mg}_u1P><4z~I4$U^{@B4h*~uJPg4eoxeRA-#lPoVDRWHQL*so z3{i3L==4$X@ac3>3GnIkQHf|dP-4Ze>7tUruj!*wz^^$)rGbHgfnRfu$^!M2 z|H0I2uKipvq5Yt+0E-5KNsrb8r92+JV3&Xeq7FNTJBB%idUk$w3<))S3knaY(%uS2 z&(33DRi2FZJUd+(JUZ|1(}x-D*7D`Txl4FTR#vO1M3{T^WwCXy|+VKkC!@`1Sf@>j1&Mg~x|lF}SRUqYz6y%l zUT}1RBXg<7|NoE}^Xxndaw)6F|HH3Ocy`|R>3rs^`P`H7xM#1sfKTTykLCv-JT#AZ zbUp_=(W}?S#s?CIKAr!-LLR+a9sd9S4^z>4pfVaP-fY2CD!cChBPex4G`Ak8bnxhW zWk zT|*O1C=-;SL_h=s0|R4K5Cda{0HZVyJI4e@ z1_lWR1_l|Z+)EJc$S2Uui zfD<2w4g&+j43K^X28Kcq?ZhY0!{ovz(Z=k^r_jve!e_vg$fw}QC*cTIuff2;Z~~-` zfq}skl+hje1lpLKdD+7Infq9JS$o);*_kC7dDuA^7*rS-7`{N&8iQ!CS{FWvW@Z;Y zg;YKXXFdT(K2R|PlElOy<5?I!{>PMIU|_~l;zCSf#Zb-*7GY(8$b&gxLI_NPexV-R7&kdm3oP+XFdT2#bPmR3-d znOBm=P+U@!lbXknR!~}!%utXDD#{pAD>6$MQX$13LtcJSZemVmRcb+ENd`kMxJ(4; zO3o+(QF+M)l^{Afv4o*06(U=ZSd^Mql9CBlPy|w#R+O5`kei*7SyTX4TwIU`ronm> zOBmwgQ;Ul7^5b*zlM_oa^Yb8T8S+YVa!OK*ax?Q1OHvtfL4IUlU;srACj$e+pZ^dr z6DrQazyQv(F!9Auadh>YpyD9)pu7xI4=cYw;yg&^pGQ&;Dl=f}ZzG8#tAC0l4r=Pb z)W1d&2jyp&_;;u{$eo}lg^B-xii6w(Dx+ZHV$gIB5=VBw5|TKm41=jRLlPH8@>e91 zIH*2>sqaA&7e!J(1xXy)-i1iwVo2&wAc+eiiQhsJ2i0*fd!HkTgUTS7_-`a}WPkC3 z0*rxy0Td3%?w3RoM>fX=NgUZd!ARoB{wjisgD8+4F!wZq1fcdxBl&9sk~p$^c0XoEclrZR}B$gyH=p_{wGw2oNgE%1NhI)n!ddc~@xv6<240?I_C8>JuexbU>C5g$| z40>SQsTuKUMTxnoP}|9;K;Z_;GN3p|HU-kxWMG&84R4qnNG*&FqCsgI6xT5IATbb5 z03`|r28It%{h;;{NH3^t0?C8&GKf|KwPhJVZ7~oJfiFPyAw(c7kQopbgaoyNKxU#V zCsw~9)P5s~ac~l*-xV$bVZiK%(I9_=*q{zDL + +int +dump_tag(yajl_gen gen, const char *name, const int tag_mask) +{ + // clang-format off + YMAP( + YSTR("bit_mask"); YINT(tag_mask); + YSTR("name"); YSTR(name); + ) + // clang-format on + + return 0; +} + +int +dump_tags(yajl_gen gen, const char *tags[], int tags_len) +{ + // clang-format off + YARR( + for (int i = 0; i < tags_len; i++) + dump_tag(gen, tags[i], 1 << i); + ) + // clang-format on + + return 0; +} + +int +dump_client(yajl_gen gen, Client *c) +{ + // clang-format off + YMAP( + YSTR("name"); YSTR(c->name); + YSTR("tags"); YINT(c->tags); + YSTR("window_id"); YINT(c->win); + YSTR("monitor_number"); YINT(c->mon->num); + + YSTR("geometry"); YMAP( + YSTR("current"); YMAP ( + YSTR("x"); YINT(c->x); + YSTR("y"); YINT(c->y); + YSTR("width"); YINT(c->w); + YSTR("height"); YINT(c->h); + ) + YSTR("old"); YMAP( + YSTR("x"); YINT(c->oldx); + YSTR("y"); YINT(c->oldy); + YSTR("width"); YINT(c->oldw); + YSTR("height"); YINT(c->oldh); + ) + ) + + YSTR("size_hints"); YMAP( + YSTR("base"); YMAP( + YSTR("width"); YINT(c->basew); + YSTR("height"); YINT(c->baseh); + ) + YSTR("step"); YMAP( + YSTR("width"); YINT(c->incw); + YSTR("height"); YINT(c->inch); + ) + YSTR("max"); YMAP( + YSTR("width"); YINT(c->maxw); + YSTR("height"); YINT(c->maxh); + ) + YSTR("min"); YMAP( + YSTR("width"); YINT(c->minw); + YSTR("height"); YINT(c->minh); + ) + YSTR("aspect_ratio"); YMAP( + YSTR("min"); YDOUBLE(c->mina); + YSTR("max"); YDOUBLE(c->maxa); + ) + ) + + YSTR("border_width"); YMAP( + YSTR("current"); YINT(c->bw); + YSTR("old"); YINT(c->oldbw); + ) + + YSTR("states"); YMAP( + YSTR("is_fixed"); YBOOL(c->isfixed); + YSTR("is_floating"); YBOOL(c->isfloating); + YSTR("is_urgent"); YBOOL(c->isurgent); + YSTR("never_focus"); YBOOL(c->neverfocus); + YSTR("old_state"); YBOOL(c->oldstate); + YSTR("is_fullscreen"); YBOOL(c->isfullscreen); + ) + ) + // clang-format on + + return 0; +} + +int +dump_monitor(yajl_gen gen, Monitor *mon, int is_selected) +{ + // clang-format off + YMAP( + YSTR("master_factor"); YDOUBLE(mon->mfact); + YSTR("num_master"); YINT(mon->nmaster); + YSTR("num"); YINT(mon->num); + YSTR("is_selected"); YBOOL(is_selected); + + YSTR("monitor_geometry"); YMAP( + YSTR("x"); YINT(mon->mx); + YSTR("y"); YINT(mon->my); + YSTR("width"); YINT(mon->mw); + YSTR("height"); YINT(mon->mh); + ) + + YSTR("window_geometry"); YMAP( + YSTR("x"); YINT(mon->wx); + YSTR("y"); YINT(mon->wy); + YSTR("width"); YINT(mon->ww); + YSTR("height"); YINT(mon->wh); + ) + + YSTR("tagset"); YMAP( + YSTR("current"); YINT(mon->tagset[mon->seltags]); + YSTR("old"); YINT(mon->tagset[mon->seltags ^ 1]); + ) + + YSTR("tag_state"); dump_tag_state(gen, mon->tagstate); + + YSTR("clients"); YMAP( + YSTR("selected"); YINT(mon->sel ? mon->sel->win : 0); + YSTR("stack"); YARR( + for (Client* c = mon->stack; c; c = c->snext) + YINT(c->win); + ) + YSTR("all"); YARR( + for (Client* c = mon->clients; c; c = c->next) + YINT(c->win); + ) + ) + + YSTR("layout"); YMAP( + YSTR("symbol"); YMAP( + YSTR("current"); YSTR(mon->ltsymbol); + YSTR("old"); YSTR(mon->lastltsymbol); + ) + YSTR("address"); YMAP( + YSTR("current"); YINT((uintptr_t)mon->lt[mon->sellt]); + YSTR("old"); YINT((uintptr_t)mon->lt[mon->sellt ^ 1]); + ) + ) + + YSTR("bar"); YMAP( + YSTR("y"); YINT(mon->by); + YSTR("is_shown"); YBOOL(mon->showbar); + YSTR("is_top"); YBOOL(mon->topbar); + YSTR("window_id"); YINT(mon->barwin); + ) + ) + // clang-format on + + return 0; +} + +int +dump_monitors(yajl_gen gen, Monitor *mons, Monitor *selmon) +{ + // clang-format off + YARR( + for (Monitor *mon = mons; mon; mon = mon->next) { + if (mon == selmon) + dump_monitor(gen, mon, 1); + else + dump_monitor(gen, mon, 0); + } + ) + // clang-format on + + return 0; +} + +int +dump_layouts(yajl_gen gen, const Layout layouts[], const int layouts_len) +{ + // clang-format off + YARR( + for (int i = 0; i < layouts_len; i++) { + YMAP( + // Check for a NULL pointer. The cycle layouts patch adds an entry at + // the end of the layouts array with a NULL pointer for the symbol + YSTR("symbol"); YSTR((layouts[i].symbol ? layouts[i].symbol : "")); + YSTR("address"); YINT((uintptr_t)(layouts + i)); + ) + } + ) + // clang-format on + + return 0; +} + +int +dump_tag_state(yajl_gen gen, TagState state) +{ + // clang-format off + YMAP( + YSTR("selected"); YINT(state.selected); + YSTR("occupied"); YINT(state.occupied); + YSTR("urgent"); YINT(state.urgent); + ) + // clang-format on + + return 0; +} + +int +dump_tag_event(yajl_gen gen, int mon_num, TagState old_state, + TagState new_state) +{ + // clang-format off + YMAP( + YSTR("tag_change_event"); YMAP( + YSTR("monitor_number"); YINT(mon_num); + YSTR("old_state"); dump_tag_state(gen, old_state); + YSTR("new_state"); dump_tag_state(gen, new_state); + ) + ) + // clang-format on + + return 0; +} + +int +dump_client_focus_change_event(yajl_gen gen, Client *old_client, + Client *new_client, int mon_num) +{ + // clang-format off + YMAP( + YSTR("client_focus_change_event"); YMAP( + YSTR("monitor_number"); YINT(mon_num); + YSTR("old_win_id"); old_client == NULL ? YNULL() : YINT(old_client->win); + YSTR("new_win_id"); new_client == NULL ? YNULL() : YINT(new_client->win); + ) + ) + // clang-format on + + return 0; +} + +int +dump_layout_change_event(yajl_gen gen, const int mon_num, + const char *old_symbol, const Layout *old_layout, + const char *new_symbol, const Layout *new_layout) +{ + // clang-format off + YMAP( + YSTR("layout_change_event"); YMAP( + YSTR("monitor_number"); YINT(mon_num); + YSTR("old_symbol"); YSTR(old_symbol); + YSTR("old_address"); YINT((uintptr_t)old_layout); + YSTR("new_symbol"); YSTR(new_symbol); + YSTR("new_address"); YINT((uintptr_t)new_layout); + ) + ) + // clang-format on + + return 0; +} + +int +dump_monitor_focus_change_event(yajl_gen gen, const int last_mon_num, + const int new_mon_num) +{ + // clang-format off + YMAP( + YSTR("monitor_focus_change_event"); YMAP( + YSTR("old_monitor_number"); YINT(last_mon_num); + YSTR("new_monitor_number"); YINT(new_mon_num); + ) + ) + // clang-format on + + return 0; +} + +int +dump_focused_title_change_event(yajl_gen gen, const int mon_num, + const Window client_id, const char *old_name, + const char *new_name) +{ + // clang-format off + YMAP( + YSTR("focused_title_change_event"); YMAP( + YSTR("monitor_number"); YINT(mon_num); + YSTR("client_window_id"); YINT(client_id); + YSTR("old_name"); YSTR(old_name); + YSTR("new_name"); YSTR(new_name); + ) + ) + // clang-format on + + return 0; +} + +int +dump_client_state(yajl_gen gen, const ClientState *state) +{ + // clang-format off + YMAP( + YSTR("old_state"); YBOOL(state->oldstate); + YSTR("is_fixed"); YBOOL(state->isfixed); + YSTR("is_floating"); YBOOL(state->isfloating); + YSTR("is_fullscreen"); YBOOL(state->isfullscreen); + YSTR("is_urgent"); YBOOL(state->isurgent); + YSTR("never_focus"); YBOOL(state->neverfocus); + ) + // clang-format on + + return 0; +} + +int +dump_focused_state_change_event(yajl_gen gen, const int mon_num, + const Window client_id, + const ClientState *old_state, + const ClientState *new_state) +{ + // clang-format off + YMAP( + YSTR("focused_state_change_event"); YMAP( + YSTR("monitor_number"); YINT(mon_num); + YSTR("client_window_id"); YINT(client_id); + YSTR("old_state"); dump_client_state(gen, old_state); + YSTR("new_state"); dump_client_state(gen, new_state); + ) + ) + // clang-format on + + return 0; +} + +int +dump_error_message(yajl_gen gen, const char *reason) +{ + // clang-format off + YMAP( + YSTR("result"); YSTR("error"); + YSTR("reason"); YSTR(reason); + ) + // clang-format on + + return 0; +} diff --git a/KleinDwm/source/yajl_dumps.h b/KleinDwm/source/yajl_dumps.h new file mode 100644 index 0000000..ee9948e --- /dev/null +++ b/KleinDwm/source/yajl_dumps.h @@ -0,0 +1,65 @@ +#ifndef YAJL_DUMPS_H_ +#define YAJL_DUMPS_H_ + +#include +#include + +#define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str)) +#define YINT(num) yajl_gen_integer(gen, num) +#define YDOUBLE(num) yajl_gen_double(gen, num) +#define YBOOL(v) yajl_gen_bool(gen, v) +#define YNULL() yajl_gen_null(gen) +#define YARR(body) \ + { \ + yajl_gen_array_open(gen); \ + body; \ + yajl_gen_array_close(gen); \ + } +#define YMAP(body) \ + { \ + yajl_gen_map_open(gen); \ + body; \ + yajl_gen_map_close(gen); \ + } + +int dump_tag(yajl_gen gen, const char *name, const int tag_mask); + +int dump_tags(yajl_gen gen, const char *tags[], int tags_len); + +int dump_client(yajl_gen gen, Client *c); + +int dump_monitor(yajl_gen gen, Monitor *mon, int is_selected); + +int dump_monitors(yajl_gen gen, Monitor *mons, Monitor *selmon); + +int dump_layouts(yajl_gen gen, const Layout layouts[], const int layouts_len); + +int dump_tag_state(yajl_gen gen, TagState state); + +int dump_tag_event(yajl_gen gen, int mon_num, TagState old_state, + TagState new_state); + +int dump_client_focus_change_event(yajl_gen gen, Client *old_client, + Client *new_client, int mon_num); + +int dump_layout_change_event(yajl_gen gen, const int mon_num, + const char *old_symbol, const Layout *old_layout, + const char *new_symbol, const Layout *new_layout); + +int dump_monitor_focus_change_event(yajl_gen gen, const int last_mon_num, + const int new_mon_num); + +int dump_focused_title_change_event(yajl_gen gen, const int mon_num, + const Window client_id, + const char *old_name, const char *new_name); + +int dump_client_state(yajl_gen gen, const ClientState *state); + +int dump_focused_state_change_event(yajl_gen gen, const int mon_num, + const Window client_id, + const ClientState *old_state, + const ClientState *new_state); + +int dump_error_message(yajl_gen gen, const char *reason); + +#endif // YAJL_DUMPS_H_ diff --git a/customstatusbar/statusbars/colorvars.sh b/customstatusbar/statusbars/colorvars.sh new file mode 100644 index 0000000..6f88f91 --- /dev/null +++ b/customstatusbar/statusbars/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/customstatusbar/statusbars/install.sh b/customstatusbar/statusbars/install.sh new file mode 100755 index 0000000..adc6520 --- /dev/null +++ b/customstatusbar/statusbars/install.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +#Check if command exits +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +#DWM dependency is running +if ! pgrep -x "dwm" > /dev/null; then + echo "DWM is not running. Please ensure that you got that shit installed and its your window manager." + exit 1 +fi + +#status2d dependency (update later if you find out how) +if command_exists dwm; then + echo "DWM is installed. Make sure you got status2D patch applied for properrendering." +fi + +#Package dependcies +requires_packages=("grep" "gawk" "procps" "coreutils" "lm-sensors" "network-manager" "x11-xserver-utils") + +for pkg in "${requires_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. Install it manually, or its equivalent and edit source code" + exit 1 + fi + else + echo "Package $pkg is installed already. YAY" + fi +done + +sudo cp /home/klein/KleinWindowManagement/customstatusbar/statusbars/statusbar.sh /usr/bin/ + +echo "Installation done. Run statusbar.sh in shell" diff --git a/customstatusbar/statusbars/oldstatus2dbar.sh b/customstatusbar/statusbars/oldstatus2dbar.sh new file mode 100755 index 0000000..bfd0dc9 --- /dev/null +++ b/customstatusbar/statusbars/oldstatus2dbar.sh @@ -0,0 +1,328 @@ +#!/bin/bash +# This is a relic of the old. Shit sucks + + +##Global Definitions +#Color definitions +color_black="#000000" #outline +color_white="#ffffff" #default +color_green="#00ff00" +color_yellow="#ffff00" +color_red="#ff0000" +color_grey="#555555" + +#Primary scheme - medium orchid +color_scheme_1="#c067dd" #background +color_scheme_2="#a656c2" +color_scheme_3="#8B45A7" +color_scheme_4="#71348C" #highlight +color_scheme_5="#572371" +color_scheme_6="#3C1256" +color_scheme_7="#22013B" #default text color + +#Complimentary scheme - pastel green +c_color_scheme_1="#84dd67" #Complimentary background color +c_color_scheme_2="#72c059" +c_color_scheme_3="#60a44b" +c_color_scheme_4="#4f883d" #Complimentary highlight +c_color_scheme_5="#3f6e30" +c_color_scheme_6="#2f5523" +c_color_scheme_7="#203d17" #Complimentary txt color + +#GLobal basics +basic_y=27 +basic_x=0 + +get_cpu() { + local cpu_stats=$(top -bn1 | rep "%pu(s)") + local us=$(echo $cpu_stats | awk '{print $2}' | tr -d '%') + local sy=$(echo $cpu_stats | awk '{print $4}' | tr -d '%') + local ni=$(echo $cpu_stats | awk '{print $6}' | tr -d '%') + local id=$(echo $cpu_stats | awk '{print $8}' | tr -d '%') + local wa=$(echo $cpu_stats | awk '{print $10}' | tr -d '%') + local hi=$(echo $cpu_stats | awk '{print $12}' | tr -d '%') + local si=$(echo $cpu_stats | awk '{print $14}' | tr -d '%') + local st=$(echo $cpu_stats | awk '{print $16}' | tr -d '%') + + local top_cpu_consumer=$(ps -eo %cpu,comm --sort=-%cpu | head -n 2 | tail -n 1 | awk '{print $2}') + top_cpu_consumer="${top_cpu_consumer:0:8}" + + local base_x=$basic_x + local base_y=$basic_y + local max_height=16 + local bar_width=3 + local status_line="" + local bg=$color_grey + + declare -A colors=( [us]="#ffd700" [sy]="#ff4500" [ni]="#ff8c00" [id]="#008000" + [wa]="#0000ff" [hi]="#4b0082" [si]="#800080" [st]="#a0522d" ) + + for state in us sy ni id wa hi si st; do + local percentage=$(echo ${!state}) + if [[ ! "$percentage" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + percentage=0 + fi + + local bar_height=$(echo "$percentage * $max_height / 100" | bc) + + status_line+="^c$bg^^r${base_x},$((base_y - max_height)),${bar_width},${max_height}^" + + local upper_y=$((base_y - bar_height)) + status_line+="^c${colors[$state]}^^r$base_x,$upper_y,$bar_width,$bar_height^" + base_x=$((base_x + bar_width + 2)) + done + + status_line+="^d^^f30^" + + echo "{[$status_line][User:$us|Sys:$sy]$top_cpu_consumer]}" +} + +get_ram() { + local mem_info=$(free -m) + local total_mem=$(echo "$mem_info" | awk '/^Mem:/ {print $2}') + local used_mem=$(echo "$mem_info" | awk '/^Mem:/ {print $3}') + + local mem_usage=$(awk "BEGIN {printf \"%.0f\", ($used_mem/$total_mem)*100}") + + local max_height=20 + local bar_height=$((max_height * mem_usage / 100)) + local bar_width=5 + local base_x=$basic_x + local base_y=$basic_y + local color=$color_white + local border=$color_black + local bg=$color_grey + + local status_line="" + status_line+="^c$bg^" + status_line+="^r$base_x,$((base_y - max_height)),$((bar_width + 2)),$((max_height + 2))^" + status_line+="^c$color^" + status_line+="^r$((base_x + 1)),$((base_y - bar_height - 1)),${bar_width},${bar_height}^" + status_line+="^d^^f8^" + status_line+="${mem_usage}%" + + echo "{[$status_line]}" +} + +get_df() { + # Fetch disk usage data + local usage_p2=$(df -h | grep '/dev/nvme0n1p2' | awk '{print $5}' | tr -d '%') + local usage_p4=$(df -h | grep '/dev/nvme0n1p4' | awk '{print $5}' | tr -d '%') + + # Safeguard against malformed input + if [[ ! "$usage_p2" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + usage_p2=0 + fi + if [[ ! "$usage_p4" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + usage_p4=0 + fi + + # Base settings for drawing + local base_x=$basic_x + local base_y=$basic_y + local max_height=20 + local bar_width=3 + local status_line="" + local bg=$color_grey + + # Define colors for each partition + declare -A colors=( + [p2]="#FFD700" + [p4]="#FF4500" + ) + + # Define percentages + local -A usages=( + [p2]=$usage_p2 + [p4]=$usage_p4 + ) + + # Create a vertical bar for each partition + for partition in p2 p4; do + local percentage=${usages[$partition]} + local bar_height=$(echo "$percentage * $max_height / 100" | bc) + + # Draw the background rectangle for 100% capacity + status_line+="^c$bg^^r${base_x},$((base_y - max_height)),${bar_width},${max_height}^" + + # Draw the usage bar on top of the background + local upper_y=$((base_y - bar_height)) + status_line+="^c${colors[$partition]}^^r$base_x,$upper_y,$bar_width,$bar_height^" + base_x=$((base_x + bar_width + 2)) + done + + # Reset formatting and move forward + status_line+="^d^^f10^" + + echo "{[$status_line][Sys:$usage_p2%|User:$usage_p4%]}" +} + +get_temperature() { + local temp=$(sensors | awk '/Package id 0/ {gsub(/[^0-9.]/, "", $4); print int($4)}') + local max_temp=70 + + local color=$color_white + local bg=$color_scheme_1 + local outline=$color_white + + if [ "$temp" -gt "$max_temp" ]; then + color=$color_red + elif [ "$temp" -lt "$max_temp" ]; then + color=$color_green + fi + + local fill_height=$(($temp * 10 / $max_temp)) + local base_y=$((basic_y - 22)) + local temp_icon="^c$outline^" + temp_icon+="^r5,$base_y,4,11^" + temp_icon+="^c$bg^" + temp_icon+="^r6,$((base_y + 4)),2,9^" + temp_icon+="^c$color^" + temp_icon+="^r6,$((base_y + 7 - fill_height)),2,$fill_height^" + temp_icon+="^c$outline^" + temp_icon+="^r4,$((base_y + 8)),7,4^" + temp_icon+="^r5,$((base_y + 9)),5,4^" + temp_icon+="^d^^f10^" + echo "^c$color_white^{[^d^$temp_icon $temp°C^c$color_white^]}^d^" +} + +get_battery() { + local status=$(cat /sys/class/power_supply/BAT0/status) + local capacity=$(cat /sys/class/power_supply/BAT0/capacity) + local current_now=$(cat /sys/class/power_supply/BAT0/current_now) + local voltage_now=$(cat /sys/class/power_supply/BAT0/voltage_now) + local power_consumption=$(awk "BEGIN {printf \"%.2f\n\", ($current_now/1000000)*($voltage_now/1000000)}") + + local color=$color_scheme_7 + local bg=$color_scheme_1 + local outline=$color_black + local color_status=$color_white + + if [[ "$capacity" -le 15 ]]; then + color=$color_red + shutdown -h now + elif [[ "$capacity" -le 25 ]]; then + color=$color_yellow + else + color=$color_green + fi + + local fill_width=$(($capacity * 20 / 100)) + local base_y=$((basic_y - 20)) + local battery_icon="^c$outline^" + battery_icon+="^r2,$base_y,22,10^" + battery_icon+="^c$bg^" + battery_icon+="^r3,$((base_y +1)),20,8^" + battery_icon+="^c$color^" + battery_icon+="^r3,$((base_y + 1)),$fill_width,8^" + battery_icon+="^c$outline^" + battery_icon+="^r0,$((base_y + 3)),2,4^" + battery_icon+="^d^^f24^" + + if [[ "$status" == "Full" ]]; then + status="F" + color_status=$color_green + elif [[ "$status" == "Charging" ]]; then + status="C" + color_status=$c_color_scheme_1 + elif [[ "$status" == "Discharging" ]]; then + status="D" + color_status=$color_yellow + elif [[ "$status" == "Not charging" ]]; then + status="NC" + color_status=$color_scheme_4 + else + status="NA" + fi + + echo "{[$battery_icon^c$color^$capacity^d^%]}" +} + +get_wifi() { + local color=$color_black + local bg=$color_grey + local ssid=$(nmcli -t -f active,ssid dev wifi | grep '^yes' | cut -d':' -f2) + ssid="${ssid:-No WiFi}" + ssid="${ssid:0:15}" + + local dwm=$(grep wlp0s20f3 /proc/net/wireless | awk '{ print int($4) }') + local signal_normalized=$(( (dwm + 90) * 100 / 60 )) + local signal + if [ $signal_normalized -gt 100 ]; then + signal=100 + elif [ $signal_normalized -lt 0 ]; then + signal=0 + else + signal=$signal_normalized + fi + + local color=$color_white + if [ $signal -ge 66 ]; then + color=$color_green + elif [ $signal -le 33 ]; then + color=$color_red + elif [ $signal -gt 33 ] && [ $signal -lt 66 ]; then + color=$color_yellow + fi + + local base_x=$basic_x + local base_y=$((basic_y - 7)) + 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=$((3 * i + 1)) + local height=$((3 * i + 1)) + local height_placement=$((base_y - height)) + if [ $i -le $bars_filled ]; then + wifi_icon+="^c$color^" + else + wifi_icon+="^c$bg^" + fi + wifi_icon+="^r$((base_x + 3 * (i - 1))),$height_placement,$width,$height^" + done + wifi_icon+="^d^^f18^" + + echo "{[$wifi_icon$ssid[$signal%]]}" +} + +get_screen_width() { + local screen_width_px=$(xdpyinfo | awk '/dimensions:/ {print $2}' | cut -dx -f1) + echo $((screen_width_px / 1)) +} + +get_status() { + local screen_width=$(get_screen_width) + declare -A components=( + [cpu]="$(get_cpu)" + [ram]="$(get_ram)" + [df]="$(get_df)" + [temperature]="$(get_temperature)" + [battery]="$(get_battery)" + [wifi]="$(get_wifi)" + ) + local status_line="" + local separator="" + local total_length=0 + local sep_length=${#separator} + + for component in date wifi battery temperature df ram cpu; do + local component_output="${components[$component]}" + local component_length=$(( ${#component_output} + sep_length )) + + if [[ $((total_length + component_length)) -le $screen_width ]]; then + status_line="${component_output}${status_line}" + total_length=$((total_length + component_length)) + else + echo "Skipped: $component due to space constraints" + fi + done + + echo "$status_line" +} + +while true; do + xsetroot -name "$(get_status)" + sleep 1 +done diff --git a/customstatusbar/statusbars/statusbar.sh b/customstatusbar/statusbars/statusbar.sh new file mode 100755 index 0000000..01ffd41 --- /dev/null +++ b/customstatusbar/statusbars/statusbar.sh @@ -0,0 +1,220 @@ +#!/usr/bin/env bash + +# Source color vars +source /home/klein/KleinWindowManagement/customstatusbar/statusbars/colorvars.sh + +# Define Basic Dimentions +base_x=0 +base_y=2 +max_height=23 +bar_width=5 +gap=5 + +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^^f5^" + 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}% : ${topcon}}" +} + +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 * mem_usage / 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^^f5^" + status_line+="${p_mem}%" + echo "{Mem:$status_line}" +} + +swap() { + local m_swap=$(free -m) + local t_swap=$(echo "$m_mem" | awk '/^Swap:/ {print $2}') + local u_swap=$(echo "$m_mem" | 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 * mem_usage / 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^^f5^" + status_line+="${p_swap}%" + echo "{Swap:$status_line}|" +} + +disk() { + local usage_p2=$(df -h | grep '/dev/nvme0n1p2' | awk '{print $5}' | tr -d '%') + local usage_p4=$(df -h | grep '/dev/nvme0n1p4' | 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 +2)) + done + status_line+="^d^^f15^" + echo "{Disk:${status_line}R:${usage_p2}%|U:${usage_p4}%}" +} + +cpu_temperature(){ + local temp=$(sensors | awk '/Package id 0/ {gsub(/[^0-9.]/, "", $4); print int($4)}') + local max_temp=70 + 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 * 10 / $max_temp)) + local usage_y=$((adj_y + 10 - usage_height)) + local temp_icon="^c$black^" + temp_icon+="^r7,${base_y},5,15^" #Bar behind the green + temp_icon+="^c$color^" + temp_icon+="^r8,${usage_y},3,${usage_height}^" # Fill Bar + temp_icon+="^c$black^" + temp_icon+="^r4,17,11,5^" + temp_icon+="^r5,19,9,6^" + temp_icon+="^d^^f15^" + echo "^c$white^{^d^$temp_icon $temp°C^c$white^}^d^" +} + +battery() { + local status=$(cat /sys/class/power_supply/BAT0/status) + local capacity=$(cat /sys/class/power_supply/BAT0/capacity) + local color=$white + if [[ "$capacity" -le 15 ]]; then + color=$red + elif [[ "$capacity" -le 25 ]]; then + color=$yellow + else + color=$green + fi + local adj_y=7 + local fill_width=$(($capacity * 20 / 100)) + local battery_icon="^c$black^" + battery_icon+="^r2,10,24,12^" + battery_icon+="^c$grey^" + battery_icon+="^r4,12,20,8^" + battery_icon+="^c$color^" + battery_icon+="^r4,12,$fill_width,8^" + battery_icon+="^c$black^" + battery_icon+="^r26,13,4,6^" + battery_icon+="^d^^f35^" + local color_status=$white + if [[ "$status" == "Full" ]]; then + color_status=$green + elif [[ "$status" == "Charging" ]]; then + color_status=$green + elif [[ "$status" == "Discharging" ]]; then + color_status=$grey + elif [[ "$status" == "Not charging" ]]; then + color_status=$white + else + status="NA" + fi + local volt=$(sensors | awk '/BAT0-acpi-0/ {getline; getline; print $2}') + echo "{${battery_icon}^c${color_status}^${capacity}^d^% ${volt}V}" +} + +wifi() { + local ssid=$(nmcli -t -f active,ssid dev wifi | grep '^yes' | cut -d':' -f2) + ssid="${ssid:-No WiFi}" + ssid="${ssid:0:15}" + local dwm=$(grep wlp0s20f3 /proc/net/wireless | awk '{ print int($4) }') + local signal_normalized=$(( (dwm + 90) * 100 / 60 )) + local signal + if [ $signal_normalized -gt 100 ]; then + signal=100 + elif [ $signal_normalized -lt 0 ]; then + signal=0 + else + signal=$signal_normalized + 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=$((3 * i + 1)) + local height=$((3 * 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 + 4 * (i - 2))),$adj_y,$width,$height^" + done + wifi_icon+="^d^^f17^" + + echo "{ $wifi_icon$ssid : $signal% }" +} + +status(){ + echo "$(cpu)|$(ram)|$(swap)$(disk)|$(cpu_temperature)|$(battery)|$(wifi)" +} +while true; do + xsetroot -name "$(status)" +done + + diff --git a/customstatusbar/systrays/brightnesssystray2.sh b/customstatusbar/systrays/brightnesssystray2.sh new file mode 100755 index 0000000..ffc949d --- /dev/null +++ b/customstatusbar/systrays/brightnesssystray2.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +icon_name="/usr/share/icons/Adwaita/16x16/status/display-brightness-symbolic.symbolic.png" + +# Persistent loop to handle the system tray icon +while true +do + # Command to keep the icon in the systray and open the dialog on click + yad --notification --image="$icon_name" \ + --command="bash -c '\ + while : ; do \ + current_brightness=\$(brightnessctl get 2>/dev/null); \ + max_brightness=\$(brightnessctl max 2>/dev/null); \ + brightness_percent=\$((current_brightness * 100 / max_brightness)); \ + command_output=\$(yad --title \"Brightness Control\" --width=300 --height=150 --posx=810 --posy=575 \ + --form --separator=\",\" --field=\"Set Brightness (0-100):NUM\" \"\$brightness_percent\"!0..100!1 \ + --scale --value=\$brightness_percent --min-value=0 --max-value=100 --step=1 \ + --button=\"Increase Brightness\":1 --button=\"Decrease Brightness\":2 --button=gtk-ok:0 --button=gtk-cancel:3 \ + --fixed --undecorated --on-top --skip-taskbar --skip-pager 2>/dev/null); \ + ret=\$?; \ + case \$ret in \ + 0) \ + new_brightness=\$(echo \$command_output | cut -d ',' -f 1); \ + brightnessctl set \$new_brightness% > /dev/null 2>&1; \ + break;; \ + 1) \ + current_brightness=\$(brightnessctl get 2>/dev/null); \ + max_brightness=\$(brightnessctl max 2>/dev/null); \ + brightness_percent=\$((current_brightness * 100 / max_brightness)); \ + new_brightness=\$((brightness_percent + 10)); \ + [ \$new_brightness -gt 100 ] && new_brightness=100; \ + brightnessctl set \$new_brightness% > /dev/null 2>&1; \ + continue;; \ + 2) \ + current_brightness=\$(brightnessctl get 2>/dev/null); \ + max_brightness=\$(brightnessctl max 2>/dev/null); \ + brightness_percent=\$((current_brightness * 100 / max_brightness)); \ + new_brightness=\$((brightness_percent - 10)); \ + [ \$new_brightness -lt 0 ] && new_brightness=0; \ + brightnessctl set \$new_brightness% > /dev/null 2>&1; \ + continue;; \ + 3) \ + break;; \ + *) \ + break;; \ + esac; \ + done'" + + # Sleep to ensure it doesn't respawn too quickly if closed + sleep 0.5 +done diff --git a/customstatusbar/systrays/brightnesssystray2.sh.bak b/customstatusbar/systrays/brightnesssystray2.sh.bak new file mode 100755 index 0000000..a6a51a2 --- /dev/null +++ b/customstatusbar/systrays/brightnesssystray2.sh.bak @@ -0,0 +1,37 @@ +#!/bin/bash + +icon_name="/usr/share/icons/Adwaita/16x16/status/display-brightness-symbolic.symbolic.png" + +# Persistent loop to handle the system tray icon +while true +do + # Command to keep the icon in the systray and open the dialog on click + yad --notification --image="$icon_name" \ + --command="bash -c '\ + while : ; do \ + current_brightness=\$(brightnessctl get 2>/dev/null); \ + max_brightness=\$(brightnessctl max 2>/dev/null); \ + brightness_percent=\$((current_brightness * 100 / max_brightness)); \ + command_output=\$(yad --title \"Brightness Control\" --width=300 --height=50 --posx=810 --posy=575 \ + --form --separator=\",\" --field=\"Set Brightness (0-100):NUM\" \"\$brightness_percent\"!0..100!1 \ + --button=\"Apply\":0 --button=\"Increase Brightness:1\" --button=\"Decrease Brightness:2\" --button=gtk-cancel:3 \ + --fixed --undecorated --on-top --skip-taskbar --skip-pager 2>/dev/null); \ + ret=\$?; \ + case \$ret in \ + 0) \ + new_brightness=\$(echo \$command_output | cut -d ',' -f 1); \ + brightnessctl set \$new_brightness% > /dev/null 2>&1;; \ + 1) \ + brightnessctl set +10% > /dev/null 2>&1;; \ + 2) \ + brightnessctl set 10%- > /dev/null 2>&1;; \ + 3) \ + break;; \ + *) \ + break;; \ + esac; \ + done'" + + # Sleep to ensure it doesn't respawn too quickly if closed + sleep 0.5 +done diff --git a/customstatusbar/systrays/expansivesystray.sh b/customstatusbar/systrays/expansivesystray.sh new file mode 100755 index 0000000..4eeb7ea --- /dev/null +++ b/customstatusbar/systrays/expansivesystray.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# Icon path +icon_path="/usr/share/icons/Adwaita/16x16/devices/display-symbolic.symbolic.png" + +# Persistent loop to handle the system tray icon +while true; do + yad --notification --image="$icon_path" \ + --command="bash -c 'if pgrep -x conky > /dev/null; then killall conky; else conky & disown; fi'" & + + # Wait for the tray icon to be closed + wait $! + + # Sleep to ensure it doesn't respawn too quickly if closed + sleep 2 +done + diff --git a/customstatusbar/systrays/screenshotsystray.sh b/customstatusbar/systrays/screenshotsystray.sh new file mode 100755 index 0000000..d6e768c --- /dev/null +++ b/customstatusbar/systrays/screenshotsystray.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Icon path +icon_path="/usr/share/icons/Adwaita/16x16/devices/camera-photo-symbolic.symbolic.png" + +# Directory to save screenshots +screenshot_dir="/home/klein/Pictures/screenshots" +export screenshot_dir # Export the directory path + +# Ensure directory exists +mkdir -p "$screenshot_dir" + +# Persistent loop to handle the system tray icon +while true; do + yad --notification --image="$icon_path" \ + --command="bash -c '\ + while :; do \ + yad --title \"Screenshot Tool\" --width=300 --height=50 \ + --button=\"Full Screen:0\" --button=\"Select Window:1\" --button=\"Cancel:2\" \ + --center; \ + ret=\$?; \ + counter=1; \ + while true; do \ + file_path=\"\$screenshot_dir/Screenshot_\${counter}.png\"; \ + if [[ ! -f \"\$file_path\" ]]; then \ + break; \ + fi; \ + ((counter++)); \ + done; \ + case \$ret in \ + 0) \ + scrot \"\$file_path\"; \ + break; ;; \ + 1) \ + scrot -s \"\$file_path\"; \ + break; ;; \ + 2|*) \ + break; ;; \ + esac; \ + done'" \ + # Sleep to ensure it doesn't respawn too quickly if closed + sleep 2 +done diff --git a/customstatusbar/systrays/startupscript.sh b/customstatusbar/systrays/startupscript.sh new file mode 100755 index 0000000..6f2a825 --- /dev/null +++ b/customstatusbar/systrays/startupscript.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +/home/klein/KleinWindowManagement/customstatusbar/systrays/screenshotsystray.sh & +/home/klein/KleinWindowManagement/customstatusbar/systrays/brightnesssystray2.sh & 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..0f900483b6cdc7d0d2ca9d62db82ede42dd9777a GIT binary patch literal 43584 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 JiGgTz{Q!I^wf6u3 literal 0 HcmV?d00001 diff --git a/dmenu/dmenu.1 b/dmenu/dmenu.1 new file mode 100644 index 0000000..d0a734a --- /dev/null +++ b/dmenu/dmenu.1 @@ -0,0 +1,199 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfiv ] +.RB [ \-g +.IR columns ] +.RB [ \-l +.IR lines ] +.RB [ \-m +.IR monitor ] +.RB [ \-p +.IR prompt ] +.RB [ \-fn +.IR font ] +.RB [ \-nb +.IR color ] +.RB [ \-nf +.IR color ] +.RB [ \-sb +.IR color ] +.RB [ \-sf +.IR color ] +.RB [ \-w +.IR windowid ] +.P +.BR dmenu_run " ..." +.SH DESCRIPTION +.B dmenu +is a dynamic menu for X, which reads a list of newline\-separated items from +stdin. When the user selects an item and presses Return, their choice is printed +to stdout and dmenu terminates. Entering text will narrow the items to those +matching the tokens in the input. +.P +.B dmenu_run +is a script used by +.IR dwm (1) +which lists programs in the user's $PATH and runs the result in their $SHELL. +.SH OPTIONS +.TP +.B \-b +dmenu appears at the bottom of the screen. +.TP +.B \-f +dmenu grabs the keyboard before reading stdin if not reading from a tty. This +is faster, but will lock up X until stdin reaches end\-of\-file. +.TP +.B \-i +dmenu matches menu items case insensitively. +.TP +.BI \-g " columns" +dmenu lists items in a grid with the given number of columns. +.TP +.BI \-l " 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 +from 0. +.TP +.BI \-p " prompt" +defines the prompt to be displayed to the left of the input field. +.TP +.BI \-fn " font" +defines the font or font set used. +.TP +.BI \-nb " color" +defines the normal background color. +.IR #RGB , +.IR #RRGGBB , +and X color names are supported. +.TP +.BI \-nf " color" +defines the normal foreground color. +.TP +.BI \-sb " color" +defines the selected background color. +.TP +.BI \-sf " color" +defines the selected foreground color. +.TP +.B \-v +prints version information to stdout, then exits. +.TP +.BI \-w " windowid" +embed into windowid. +.SH USAGE +dmenu is completely controlled by the keyboard. Items are selected using the +arrow keys, page up, page down, home, and end. +.TP +.B Tab +Copy the selected item to the input field. +.TP +.B Return +Confirm selection. Prints the selected item to stdout and exits, returning +success. +.TP +.B Ctrl-Return +Confirm selection. Prints the selected item to stdout and continues. +.TP +.B Shift\-Return +Confirm input. Prints the input text to stdout and exits, returning success. +.TP +.B Escape +Exit without selecting an item, returning failure. +.TP +.B Ctrl-Left +Move cursor to the start of the current word +.TP +.B Ctrl-Right +Move cursor to the end of the current word +.TP +.B C\-a +Home +.TP +.B C\-b +Left +.TP +.B C\-c +Escape +.TP +.B C\-d +Delete +.TP +.B C\-e +End +.TP +.B C\-f +Right +.TP +.B C\-g +Escape +.TP +.B C\-h +Backspace +.TP +.B C\-i +Tab +.TP +.B C\-j +Return +.TP +.B C\-J +Shift-Return +.TP +.B C\-k +Delete line right +.TP +.B C\-m +Return +.TP +.B C\-M +Shift-Return +.TP +.B C\-n +Down +.TP +.B C\-p +Up +.TP +.B C\-u +Delete line left +.TP +.B C\-w +Delete word left +.TP +.B C\-y +Paste from primary X selection +.TP +.B C\-Y +Paste from X clipboard +.TP +.B M\-b +Move cursor to the start of the current word +.TP +.B M\-f +Move cursor to the end of the current word +.TP +.B M\-g +Home +.TP +.B M\-G +End +.TP +.B M\-h +Up +.TP +.B M\-j +Page down +.TP +.B M\-k +Page up +.TP +.B M\-l +Down +.SH SEE ALSO +.IR dwm (1), +.IR stest (1) diff --git a/dmenu/dmenu.c b/dmenu/dmenu.c new file mode 100644 index 0000000..4100090 --- /dev/null +++ b/dmenu/dmenu.c @@ -0,0 +1,838 @@ +/* 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) - 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..9ce2f581427981bb265f31131b76e7a356ff605b GIT binary patch literal 34072 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 +#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..59bfd109691032f98f9faeda496ad29b75d874dc GIT binary patch literal 10872 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 literal 0 HcmV?d00001 diff --git a/dmenu/patches/dmenu-border-5.2.diff b/dmenu/patches/dmenu-border-5.2.diff new file mode 100644 index 0000000..515c3c8 --- /dev/null +++ b/dmenu/patches/dmenu-border-5.2.diff @@ -0,0 +1,37 @@ +diff --git a/config.def.h b/config.def.h +index 1edb647..dd3eb31 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,3 +21,6 @@ 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 27b7a30..7c130fc 100644 +--- a/dmenu.c ++++ b/dmenu.c +@@ -684,9 +684,11 @@ setup(void) + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; +- win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, ++ 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); + + +@@ -757,6 +759,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(); + diff --git a/dmenu/patches/dmenu-bordercolor-20230512-0fe460d.diff b/dmenu/patches/dmenu-bordercolor-20230512-0fe460d.diff new file mode 100644 index 0000000..6bf629b --- /dev/null +++ b/dmenu/patches/dmenu-bordercolor-20230512-0fe460d.diff @@ -0,0 +1,71 @@ +From 8dbea77ad7b320d9c7e07990274ea74835785084 Mon Sep 17 00:00:00 2001 +From: Markus Hanetzok +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..99a49856b62a4e9b346ba1f5c576b76357c451b8 GIT binary patch literal 16888 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 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..0700d9b8f52684dca89c58cec6129b3f9161df7d GIT binary patch literal 4952 zcmb<-^>JfjWMqH=Mg}_u1P><4z%YX!!FB*M9T@l-_!t5_x@|#Rk6u#{Wq81&TNK22 z0V4J@F)%QIS)m@CUxPh5e|t2((O_g?@aTL77H&OI!ftrrNS2~~y<3=Fmm2@DLS7qLkwFff$vz$W3qz)(6LDpAS}Wt3>X7B>9m(Rm-{ zgy%0S{{R1vWKjbH1AlAE|NsACI=H{Ns7SK#w*(->5XQN_HuUJ-qB4O2R=w<~4kcXv|szq;KZfHiRr0U3aZ5l(YlFA$tkI6=@~f%g+;}sW#tvI3emcG3Tc@+sUTK9j0F|v9^K3ytq1s9elRdFG+5~Jx4vg!VCV{9Y`MhW@&qLIOSe?8p@NB_l-r~E zFvtJ1FVBHu;r{{8&O08R%!UViyLD8WKQKCWg)+2U;-7l3UEps(pxsa`3GZpf@4E1BSR^_Z+DIghi~iKQZ8_~dHz58n(hBt&*mc>4Zrd{ zdckh<>3r{-{0*81J@{QefR(l$fJByaC(HIyc5rmQ*6`_sg#YXJ9*_V4EA?nSP_n=` z`J3SZ=i@BfLGk4IpGD=R=l}oz|DW~jjZxuv`RCvN|E+IJ**!qK*GfK+4F5XSqt}J~ ze}e^ADf2M~29ItV6_3t;5Hleqfk)?akIs8g5gQec)&uotz`WO0V9K-eD9H8%n8Q3; z50uKmIpB~i34o?2k8bM{^VcVQdJ}|wdTUe!Ji56J5BPRI@ag=tk3oQe!KV|F+-(2z zGcc5|_33;D6M4PBquUzhvezpN4?s=ipK`#X@dzl&qhlRo9Ah2h9ODl&xH~&rDQLK) zCS@k(DHs~*8R;458k%TAnV`ff0wNd~7#OR97#J%A7^QjGIVLbNfE>Xf1C>(;(T;oq z8Vn2!1|T*A1A`1G+c@$Gv@yBxNi;Ki@f~1d=3?TLaO4wk!bGiBG|aPr`{$ zzzJj))EKN3D2iDaKK{oh$&9rIU|?Wo0F^7KIye~^7?_aMgZ&Ij%V1^T#0h4>2vE3z z)Wg()!-Ii=K@t>}3@i)@Acrw9Fl0bA$U(&wpnOm%3t}ok#Sefu3=9k~aW$y;38*>9 zE@oz6gQ`YQI$$O%gB_Sg5QbnTD?3=9lHNZ|lZV+;%o={VHq;SevyAzqC`ycvgh2M%#id}8zO z3>@kg;t*#jE=ese(Mx6kS0oI2KF$UVAQ6VNoWyj7ywq}r{G1eq+{BXP43MNDh%jO( zE=fr(Dq=`1N-r~DNGmAH%qvM_NUg{$VMt6)PAx8G$SE#KEMX`pEh%QmP0Y+=C`c_T z$}eIlE-6aR08uHKc?{{PB{`XSsSIgFsi_S41*v%{nMDjmsfj5dIyoo5I2FVw&Vw2c ziB(v71!a9c1_p*dpc451|Nq<&X$FuuC|AM6LHP+J1ri4}(O}}B`~(sQnFk6-n7A@X zkbwc5QU#IBw?YyZLJ|k1Q;-zMd}am)hG=NMiUJ9O^FF9tU|@)ciWj4aCqu=1(8SZB z;!DxQv!UYa(8Tkh;(MXuAb$xXx$^;39OlkqsQNEx;^k2Bzi8t9P;ntB4Kg1Twy62NkDLxQq2eG4WX=JodQ*@9)Lu{r0~Rmr(0BkzgVe`B)x+vrkT`O_*p8$g zBnLC+43ap=oIOzfO(+dg3lfLb?T?`1AonBZ7g&V~5*J5GpBzwkfz07S66b}AgVakP ziMv6?LF##t#1oOkL2|JCoPi_`%KI>X!SVsfd?_Sz8lmbz;?hXs9Z2FJzrxI!fg~=A zq<$%qIH<0MsfVR6kQ+ha)(%y72TFtNl}9q?9aJ2o9)w~3`i(=J6{H9$T^)w1gS8Vt z(jW|tCI%BI10)8*u=Ym+k~pl41qd2@H78N8B(gZdCK zHb@@~n?m)&_%Iq>Z6VZts54>87%ZUrLBkqwK?Vi}nEPR3Inc5i6b>*k5PbpEr~&nh zK^$oM;X=|6^FPQv=yrp&Jb>EI08JR6vJqq?%zl`DP%?!o0cnHd2T+Bp;35zP$UX=Q aLW1ffZ2BcYl?ej_1E`$~Q3)Z@^#cGfJunsk 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..04ef62bd873819e6d950e2a9fdef20a8e866ae30 GIT binary patch literal 2080 zcmb<-^>JfjWMqH=Mg}_u1P><4!0>2`I)8gKzInjFz~IqYqGI9E z8KUCg(dnb&;nV4&65!M6qY}|_pu~z_(?unLU(-jWfM0WpN&^D}1Ha}Rl?h;aiOK>n zy+&mNnBJmt08H;uxd1Y@w?*Xxi0-xp+3V4H&!aa)MZ%-gMMc4**G0wPxQmJe2Ll7c zaTgUC1CYW_7Zr^cAhiwF3?AL4AXbTvN9TQz13ADF9^EA>91S(h|E)@e_bV_kFn~o3 z|AVR5T>H6TLi<5s0TvAelOC-HN_jkb!7c#{L>*2}%*n}5wqjs#cXqZ?&~Qmj%1q2t zFf`IL(lgLCG|_}IL9Sq6U=V@Qj8#Dlj1>Zm(md=O6Brp7Bp4VNWT0{{L9`>EKr@pU z-v_9i1xSp6fk6^PJMsyH^MSk!;$X)hce5~j{EtnR8A}X9Ok>4R&db2SAcUkI>^24l z20;b}1_2D!5Px73SHz)S1BbXC4slRyVl&?g>Q0#Zz~RKez+jET9EQ@8%pARB20b5V z1BR5$REFY`l+>akhO)GRqRhOKG=}1mqMXz`hO~mxl4OR0)S{yNB8Jq8%o2uFNQ6WE zo(rX!7#J8pq4(!M1k8krgR%`90|Nt0d@)oUUHvAgI7mG^lKK-!;-EN!nR6aVoD)g? zZ6tAI^-qz+L8%*N&TAxb9wc+VL&ZVvWJVJI0~H6k6NF*rfHDzC3Zx!{L2MAV0TB!g z3?MNOhQ$wqUU6k^Nn#R%UU5kggwBAmic)hD^-5AJN*MG~5=#;p^pc8;8T5+sK^%~B zLp?(Vz2yAd+|;}h2EDxel2ko+zfj%clEma}20gIu)QtGFqQu-(sO{uaAb)}K5-1Ll zO@Wkw3=B*lNhAq0E`u}!0|O|WVCrE?9YBG~z`(!()c}eU5EB$92sgKLf53!a&y#0PaixP5=M^ 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..604e622f1a1b88310b2df70d2704ca63e5484950 GIT binary patch literal 57104 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 aATG$yFdAkzh!4Z%Q2WJU3ZW8cTm}H4WNV24 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..2e6c61369165993067fc504b45e12bdba124fb9f GIT binary patch literal 110696 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

  • ZE3Ve!t3CJwt_Lj+Ac1e8cXcN;>*L1uvNe1YXJ zGpIN$oxsA=7EK%$o=#A4kU5G->C*=)4l@Uqzk<-jVdh7oiNnl~gNlR92i+M4b7v}4 z9A-YOo~=X^ho#R3G;vruZ$lG@rH39gaacN>h9(Yk=WM7r$eqea;jjoQ4s$20-am^b z4m0NxR2*auXssyBzc-=caC1PF0|NuY12l1%InU9=Vd4A-O&k_JOrXWnNa+d|KAdRc zF!%F8#X;^wo-Y%Hio@IqYu`#Ei6iGDH6(G6S+IO$fF=%WSDT}W!~AOl6$hDt+%9#4 zio@Ik^RFM8ILyBZXyPz?)6v9X{>noWhq0C7!}8Z`G;x?a7omy6+_@Yo4sxd&k~`Ny#bNG&_20LmiNnmfghTu~R2*bJ z=!_s(dVT^GhnWvcSFh2;Vdj5;ii6D2Kyv3Fs5r~gnX1+C&I7ko7d?z$6Hs#6f1k%zpQ+ZADG;xKo{qKU)IPlAer z%s^g0nE@4tnGb7E7DB~A>Oo-x3;$ZEI7~gv-WD`*n7y4)agaH>NZ~UTDh@LTrhXPw z9Hd?kN&P~oI7~fk-PCHRI7mJ6JoP51I7~fgEfgqU?174-t3M1Chp7kcF$SqWhb9i% z+XfQ9ha?U<8wM6WPm#nycEJ4g3Mvi~LyliY=z;{8y|DFoylCREa27!lZ$(O1YEW^I z`N-?BO`+l-^V^WrTS3J^>h+Pr*&a#U07*OzDh@ISqzBe-Dus%J#E|>l)1l%Z^`IsW ztRFfLO&s12MG^;@1#7>pgNlR143XTk1u70QA0!9sryPcggT#>Ca}p{JQx9uLeL)k4 zttVlIUQmi|jw)0fW)4(4g9%g|U40l-9Ht&R{KAk56-QU!4i$%~hm8+UgNmc8-vJee zsfYCojzh)K)t`fkgVZCB!(2rY2c0DbOV9VA;vjR7=PUn0#X;tP=GbBCS?oZ$1F>!p zn(P_)(8OW>l0p-Qr4wx=anMNBC@AoU0=q1*1^;*+4_ zu<Dh|8P5F`e|0#I>S zeg%nvun1HfcF!S541^`1;;?cHBnHBdpyQ07wf)HPr~p+D%TFM+AS@3ZKSDmg6eO+z zRS!$QATbcmg{p^@b09Ggo&fbIZ2S@=2Es?7;-Ixg$nLR#nh#qi0#XaY($MgS-CGC} z17QcKdeB-3WOH1g;;?&QKx#oa9cm72dp-3z#_U{$*JDgo(rM zeTJn&ba7bvfr-QJmxsk8OdPcK5fm=4@Pvtj%5;!8tbGR)hu!B3+7khihl#`P=?1N} zLl%eKe+^nQhAa-dPaHIdh%64fe;hP61rrAyT?4zX7Ssns7M~71FA~)DKo*|^6}JF6 zkO6cjFara~oW)Ra2Q+cmIhC+;fI!k9^`QH{K<2{E8-|JRf|>)nZyhEMyKgrEB!Fb^ z0jT;6H1Xq5amfAg2zQ=_idUeizX%m?Koh?K6^Gpq50VDC{{d8d0!RSKJujf*uyIF_ zG)VnhsJH_3966Xc=$=VXIB1}W|AwkJKof_Bg9Vy6EFO{b3&>tZP^MvEfZs0-vzHAj zj-1Xx>Up8!uyPEhUJxpdoSs4I#h~KI=@}$01r-PF=?3`^W{w!`gsCXUx20_mLEa)(ITsd zoudu9cNSS3RxVvYGv^J|{10g2u>2qZJ;xR1{_jxr2592{pyB~&;;hhgS%4-E8~2`o zCN2zBzX45L3Mzg9T^wrf2Q+b5xheqd@W9-!2sH1UJnLA%?UtL9|;u)-G7Pfo&>1) z1T^*OQ1K0D;(1W<3uxk{Q1K6F;x$ll0qA+Bu<(b~I|gXtuzDu|O}rUu4(!}lm^ocg z@d;?^Cql(Hpo!0bieEqzpAQxPfF`~SDlPy$rxa%ITBx`InmDW;gq^brQxB^L3((YW zftmxlUl2K7c0k29ps9zIZx_(SVfDxdG;vtHBLMB#!`uU_UkuR1Vf9M@nmDX}DL@m4 z)h`p!#9{Tz1~hS4{c-_K99F-4Kof`6F9Oi>KVj~N)h`BU;;{N908M;1G&~E?#P>qQ zC!mSL+J_s^#9{5j3uxl7_TdLKaaj9M0Mr;ls()ea76UYKSi2iLK>d3GO&r!9`+z16YmW&)&#Q!m|8b}}25922c2NMDIILX+J8u(a&N-+# z6VTLOf{JfI6Tb-+zknwG04n|gP5e1jTmaeufZ6*FDsF%#4!d_D08RWSRDA)OICx_q z0|UbZG;wxlJ7xo#I3HB}0-CreRQv;)xC~TW0JPa2Dg7uw#SPHJ)uG}6XySTM@d7k) zQ>gd^G;tfK_y#m_XQ=oEG;w#R_y;s`Kd86>^gKXV_=iHp4ba46pyB~&;>l3)0yObV zsQ3gl@j|Hh1~l8XN0z=1)%4)!Q9UY6*oW=7leujpovRD#bM{s z!OT&FicdgOuMQR8fF`aF6~BNcZVnayfF^Da6&HY>lLoWb9V%{sChiLr4?q(Sg^CxT ziN`|4C!mR^Ld7?riRVJaFQADRL&ZO!iC06#1)%4K!Q9^r6*oW=?}my8pozoEbJ)3U zFmqt#>I5|Pu<~sKnmDYSxqv1PD+fNHiNo@#0CYkEW-lxs8K8;7(s=-yI4m8)&MSkN z14}0p(A2}?aRZt-ESxW(iNor(4`|}B`b7YGP8Zx>Xnrw36Ni zaZpo*AF3X9A2moER0L)~`-9NxlmR4e1R`MOKxvQ=?2a&y5c0SmY^?`K9C_T&6r>3x z2&(s?G)M?!F7*H@!lE91oNzL<{DH+k%wCxJ=;Md5_G$+X^U=o@r$Wt#1~+_u z5E^U@=<|ut?id4XJ_TeA2*c)G(C6bqp$5Uy8%Qk(!{$xU=d)n*Cg}4`pu4+3M#0uo zgUkS7Sh|9pa|RLvVPQ}}F)%QI&P)L51BrofHB=n7z5^r%!t?7zm3)+sUwX zKp-&?&VY);(i=z&goU8v9S@-9fy6*~5wsr#8#e}tfiP@d36_39Vj#>2DhwDH7(zfC z=(;HoGZHEe>kom%KzJckd0 zgC_nND$W7Te;~JjurIV=(m@k{2o;Boi-OdGa4B@(V+xx3dZ_poH1P`P{=Eli;*8LO z^9P!E98?^3-xkPD5MBut*MW{Vg2X`hI#k>NP5doXJOWLe3%b67L9e(nw@zM~jj9Gf7rxh8rhaBgyO2@1!8K`%K!H#aq}gh4Maza&-9-7i$PxFj(-8){2x zMtoXPVs0v^Y^MeW)qkKO8=B1FM#9#?ltb4kz$8FwVQdf$I<6E{*xydtvKDz=8}63?TQy*dQ8|{6S`- z>vymR$uclVph=+X2PJQi{m|{b5KW+So1pqd(EgH8bw40BnlgqD?jTtP23S20;X%&zgt0+1=qyrf;co#t{D*;o0XB~a z3V%@10ka<#{)y1G7+NqfEPyt2VEGSZ7YL(=AE@1f&3=tvAk_>Euz6LG{h%z1Zhs{X z`yT{C^gBTN6%Za~{DamVVY5F1v{{0If#C19 zx{CtF28qM42UI_d52Ml5g3fRSi9v%Iri=lme+MYh!9^JuVD5$qf!3&l!V;8^VPYV9 z1+*azn;!OX~XmtGm@bJ`& literal 0 HcmV?d00001
  • ZMKic)j)%TgIiic?EcGK)$WO43Smav-eY^2CDTDQ;tli+7)p|JQWJ|HE@3Ds zC@m>TOad8FoR?StqVw{Lauahht5O+CQc}~B^Ksop zP6^o7%&OFs)HJZSbMsSD8HzL0lQVKs7z#2|7>Y|uDjAYr6{o+O%+0nA+@3)zc>|SS4nA6 zdTL&BB|}AKF5GdMxtV#zC5d?@nV`fDb_U2YhQ#9J%*=Rjibzb#fjST*Q_N6WoSFh= zfs#*3YFc7xP6?b}k(rxckebI(k&&5KQp}KFP!gY)n41b_B@FMlwSa4=H{0crxv9a!88Nk{=!I_ksUy_-h2N6h2hOmn>@{3B6OG}Cwl8RDuQWJ|)L20ufvA86a zAvq_r0K!TtC`v6ZX2^z8sg(>BrFo!u0eOxgxhS<5qexdQM9#BCKmmr3CcOQQzN1u3qH@9HdkoXWsCm+{%hIp4qKSy6r zXNC$;?uBIU%;LlxP(}omH4)B5sfi`2p3V$z$<7&xMZu{hjwvZ17K2-IKw?QrYEhmG zGy%uQ7grXS#TRF$=OyMixchiIImd(6RhlxSlolAF2tbW>%g-xuE-fm~FJg#rD@sje zfTfajhO&~(l+-+i+|=CS)DjfE6~!fqMJ1rb42qo8iV|={=Yf-88j8wnq@trZwWK&R z1w}d{tpx0V98kJ1NlYp(NK8&GM)Fr_K~82eD7k|Qi}=K%^kRl&kYW^5GV_y5P<@-2 zlwXAEuUt&CiPm`#3(HA;Kp=KfAOb zxTGjEFCC%MF(t(%zZ8`FQY%uE%L?Ge8R?mVvO->JIYO~}Y6(bOXH!&wCKN;0s$)FsXRFs;SjUrxLSzMBuiy{P$T4?15F0sIwC?YfuRBwUW z3ZA~6h{DM^F()S}F*%zdJ+%as)8kVsGEt)gn))D)MEDcr&`i%S!8V2L;>9aKbT79{5vR5C;a<>x~~iy9XSVsQ9``$FS9Gqsoj6u0F_ zMMiLHNw}|Pa(Nl5IdC11*xv^NUhaQgbqM zGfPs7pzbaQr{Mg8Vupyc5|^UHa(7T01(g1S3sRFA;Axa0K0djmvLH1+DIPf`8-v6v z5<&R}+)P4oDkvqn8Q ziJ2vsSsz>*x;umNX=+{>iW#tQ09yxYyn&+(QoB`vx+29H;7SWx$0Z}xH14S-o_Qsy z>8V99&w`pY@$soeMS1z4^p*%}pP<_2mQz}s!BCNunv(+x)09%wVn04Uskj)L13>ZU z=?g8s=<=rR0R%LP>Mh=wR9K;(n}kxAgZhTBoDIryuApX3NpWaNngv5VsDTp!N+3mfjwSiIp!Pl}H8WHc zmlUOx7Jys>DzHF4fi}s&&G^jZ{5(jt4hlX93lt|l`N@enslNFsnQ57za1IN5gnMd! zZYpRbg+Z^lG8Z&fpjTW{1fervEO0h0V9?9UFGShJcw4vU=TzHj1AHS zvP-WhAMCEg+{|PKkntdE81z8W40=h$#SD7M`MJ5E_6~f|i+l#?_+`+@K6G0Mg9LP= z0E~v6OAWIDq!z{o(aH=A;Ip7%`a$c@LER{zk)Ex3=j>&pzSiC zv$J9PVdrJTXg#O~nEPSsV00Y=1H(Tg|HIDXhS9L~>*(%>+20G^zXw`-3lV{x2M(iQ z=YhlQNB2KyKNToWoFLla=ZnM68Hb%m4$===bAqmaA$Xr10|Q7NYy|kwSqAtxwK!N1 zL4fQ+@E90CcR+)J2&P{Fsvkx_fLRFD1iIT7#DvkH?J~&v8KCPUVKka{1{fbkZ$NWD zY<(q+{yht18v?`Zg^7T+e<0frTh|ApIbjN+G`j!yK<$U62heJ1h*FTL3=FXK;Se#* zHCv$V4htcTjVSE_<0kg zX3)AHBsPf2$OuWhF#WJ~)i7EB8nB@J2T~7n4=f#kmct?2zX5ulKa7U$F9qoZVVHgx z4cd2#tRHqC0E})xi!hiz7!5iW4_QBK{WqM3EtmjVj>Z3=?foFLVE%`%KZ349l1PJU zgi`3`IVib+>^FeQ!zrkKdAI{xX}][{+-}{+-}]. 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..0513d882ec824457d8e973a36a176897655a95ca GIT binary patch literal 87168 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 `^S98LTP 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 YEn{Flj7ac@sDzzU0uzUb!gve}0F&qgj{pDw literal 0 HcmV?d00001 diff --git a/st/win.h b/st/win.h new file mode 100644 index 0000000..6de960d --- /dev/null +++ b/st/win.h @@ -0,0 +1,41 @@ +/* See LICENSE for license details. */ + +enum win_mode { + MODE_VISIBLE = 1 << 0, + MODE_FOCUSED = 1 << 1, + MODE_APPKEYPAD = 1 << 2, + MODE_MOUSEBTN = 1 << 3, + MODE_MOUSEMOTION = 1 << 4, + MODE_REVERSE = 1 << 5, + MODE_KBDLOCK = 1 << 6, + MODE_HIDE = 1 << 7, + MODE_APPCURSOR = 1 << 8, + MODE_MOUSESGR = 1 << 9, + MODE_8BIT = 1 << 10, + MODE_BLINK = 1 << 11, + MODE_FBLINK = 1 << 12, + MODE_FOCUS = 1 << 13, + MODE_MOUSEX10 = 1 << 14, + MODE_MOUSEMANY = 1 << 15, + MODE_BRCKTPASTE = 1 << 16, + MODE_NUMLOCK = 1 << 17, + MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ + |MODE_MOUSEMANY, +}; + +void xbell(void); +void xclipcopy(void); +void xdrawcursor(int, int, Glyph, int, int, Glyph); +void xdrawline(Line, int, int, int); +void xfinishdraw(void); +void xloadcols(void); +int xsetcolorname(int, const char *); +int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *); +void xseticontitle(char *); +void xsettitle(char *); +int xsetcursor(int); +void xsetmode(int, unsigned int); +void xsetpointermotion(int); +void xsetsel(char *); +int xstartdraw(void); +void xximspot(int, int); diff --git a/st/x.c b/st/x.c new file mode 100644 index 0000000..9d752c0 --- /dev/null +++ b/st/x.c @@ -0,0 +1,2107 @@ +/* See LICENSE for license details. */ +#include +#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..330e1271fc363b2f306198b45b956034d033f3b2 GIT binary patch literal 73896 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