From fa78de207bd6c83d0b038e7fce426540df1563cc Mon Sep 17 00:00:00 2001 From: sML Date: Tue, 10 Jan 2023 16:40:49 +0100 Subject: [PATCH] First --- README.md | 3 + dwm/LICENSE | 38 + dwm/Makefile | 51 + dwm/README | 48 + dwm/config.css | 124 + dwm/config.def.h | 125 + dwm/config.h | 124 + dwm/config.mk | 39 + dwm/drw.c | 464 +++ dwm/drw.h | 58 + dwm/drw.o | Bin 0 -> 11472 bytes dwm/dwm | Bin 0 -> 73016 bytes dwm/dwm-autostart-20210120-cb3f58a.diff | 179 ++ dwm/dwm-fullgaps-20200508-7b77734.diff | 138 + dwm/dwm-preserveonrestart-6.3.diff | 118 + dwm/dwm-restartsig-20180523-6.2.diff | 139 + dwm/dwm.1 | 219 ++ dwm/dwm.1.orig | 196 ++ dwm/dwm.c | 2302 +++++++++++++++ dwm/dwm.c.orig | 2218 ++++++++++++++ dwm/dwm.o | Bin 0 -> 61120 bytes dwm/dwm.png | Bin 0 -> 373 bytes dwm/transient.c | 42 + dwm/util.c | 36 + dwm/util.h | 8 + dwm/util.o | Bin 0 -> 2216 bytes hypr/hyprland.conf | 164 ++ hypr/hyprpaper.conf | 3 + kitty/kitty.conf | 244 ++ st/FAQ | 250 ++ st/LEGACY | 17 + st/LICENSE | 34 + st/Makefile | 57 + st/README | 34 + st/TODO | 28 + st/arg.h | 50 + st/config.def.h | 494 ++++ st/config.def.h.orig | 484 ++++ st/config.h | 494 ++++ st/config.mk | 36 + st/st | Bin 0 -> 111136 bytes st/st-alpha-20220206-0.8.5.diff | 146 + st/st-dracula-0.8.5.diff | 81 + st/st-scrollback-20210507-4536f46.diff | 351 +++ st/st-scrollback-mouse-20220127-2c5edf2.diff | 25 + st/st.1 | 177 ++ st/st.c | 2727 ++++++++++++++++++ st/st.c.orig | 2656 +++++++++++++++++ st/st.h | 129 + st/st.info | 239 ++ st/st.o | Bin 0 -> 80992 bytes st/win.h | 41 + st/x.c | 2116 ++++++++++++++ st/x.c.orig | 2096 ++++++++++++++ st/x.o | Bin 0 -> 76680 bytes waybar/config | 123 + waybar/style.css | 53 + 57 files changed, 20018 insertions(+) create mode 100644 README.md create mode 100644 dwm/LICENSE create mode 100644 dwm/Makefile create mode 100644 dwm/README create mode 100644 dwm/config.css create mode 100644 dwm/config.def.h create mode 100644 dwm/config.h create mode 100644 dwm/config.mk create mode 100644 dwm/drw.c create mode 100644 dwm/drw.h create mode 100644 dwm/drw.o create mode 100755 dwm/dwm create mode 100644 dwm/dwm-autostart-20210120-cb3f58a.diff create mode 100644 dwm/dwm-fullgaps-20200508-7b77734.diff create mode 100644 dwm/dwm-preserveonrestart-6.3.diff create mode 100644 dwm/dwm-restartsig-20180523-6.2.diff create mode 100644 dwm/dwm.1 create mode 100644 dwm/dwm.1.orig create mode 100644 dwm/dwm.c create mode 100644 dwm/dwm.c.orig create mode 100644 dwm/dwm.o create mode 100644 dwm/dwm.png create mode 100644 dwm/transient.c create mode 100644 dwm/util.c create mode 100644 dwm/util.h create mode 100644 dwm/util.o create mode 100644 hypr/hyprland.conf create mode 100644 hypr/hyprpaper.conf create mode 100644 kitty/kitty.conf 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.def.h.orig create mode 100644 st/config.h create mode 100644 st/config.mk create mode 100755 st/st create mode 100644 st/st-alpha-20220206-0.8.5.diff create mode 100644 st/st-dracula-0.8.5.diff create mode 100644 st/st-scrollback-20210507-4536f46.diff create mode 100644 st/st-scrollback-mouse-20220127-2c5edf2.diff create mode 100644 st/st.1 create mode 100644 st/st.c create mode 100644 st/st.c.orig create mode 100644 st/st.h 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.c.orig create mode 100644 st/x.o create mode 100644 waybar/config create mode 100644 waybar/style.css diff --git a/README.md b/README.md new file mode 100644 index 0000000..c381bc2 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# DOTFILES +Here can be found my config files. + diff --git a/dwm/LICENSE b/dwm/LICENSE new file mode 100644 index 0000000..995172f --- /dev/null +++ b/dwm/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/dwm/Makefile b/dwm/Makefile new file mode 100644 index 0000000..77bcbc0 --- /dev/null +++ b/dwm/Makefile @@ -0,0 +1,51 @@ +# dwm - dynamic window manager +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dwm.c util.c +OBJ = ${SRC:.c=.o} + +all: options dwm + +options: + @echo dwm build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + ${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + cp config.def.h $@ + +dwm: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + rm -f dwm ${OBJ} dwm-${VERSION}.tar.gz + +dist: clean + mkdir -p dwm-${VERSION} + cp -R LICENSE Makefile README config.def.h config.mk\ + dwm.1 drw.h util.h ${SRC} dwm.png transient.c dwm-${VERSION} + tar -cf dwm-${VERSION}.tar dwm-${VERSION} + gzip dwm-${VERSION}.tar + rm -rf dwm-${VERSION} + +install: all + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f dwm ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/dwm + mkdir -p ${DESTDIR}${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1 + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/dwm\ + ${DESTDIR}${MANPREFIX}/man1/dwm.1 + +.PHONY: all options clean dist install uninstall diff --git a/dwm/README b/dwm/README new file mode 100644 index 0000000..95d4fd0 --- /dev/null +++ b/dwm/README @@ -0,0 +1,48 @@ +dwm - dynamic window manager +============================ +dwm is an extremely fast, small, and dynamic window manager for X. + + +Requirements +------------ +In order to build dwm you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (dwm is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install dwm (if +necessary as root): + + make clean install + + +Running dwm +----------- +Add the following line to your .xinitrc to start dwm using startx: + + exec dwm + +In order to connect dwm to a specific display, make sure that +the DISPLAY environment variable is set correctly, e.g.: + + DISPLAY=foo.bar:1 exec dwm + +(This will start dwm on display :1 of the host foo.bar.) + +In order to display status info in the bar, you can do something +like this in your .xinitrc: + + while xsetroot -name "`date` `uptime | sed 's/.*,//'`" + do + sleep 1 + done & + exec dwm + + +Configuration +------------- +The configuration of dwm is done by creating a custom config.h +and (re)compiling the source code. diff --git a/dwm/config.css b/dwm/config.css new file mode 100644 index 0000000..7895c1e --- /dev/null +++ b/dwm/config.css @@ -0,0 +1,124 @@ +/* See LICENSE file for copyright and license details. */ + +/* 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 */ +static const char *fonts[] = { "monospace:size=10" }; +static const char dmenufont[] = "monospace:size=10"; +static const char col_gray1[] = "#222222"; +static const char col_gray2[] = "#444444"; +static const char col_gray3[] = "#bbbbbb"; +static const char col_gray4[] = "#eeeeee"; +static const char col_cyan[] = "#c27ba0"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +//static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +static const char *tags[] = { "dev", "www", "sys", "mus", "chat", "misc" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static const char *dmenucmd[] = { "dmenu_run", "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +//static const char *dmenucmd[] = { "dmenu_run", "-c", "-l", "15", NULL}; + + +static const char *termcmd[] = { "st", NULL }; + +static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_d, spawn, {.v = dmenucmd } }, + { MODKEY , 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_p, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY|ShiftMask, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_q, 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 } }, + { MODKEY, XK_minus, setgaps, {.i = -1 } }, + { MODKEY, XK_equal, setgaps, {.i = +1 } }, + { MODKEY|ShiftMask, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_x, quit, {0} }, + { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/dwm/config.def.h b/dwm/config.def.h new file mode 100644 index 0000000..ba91a0d --- /dev/null +++ b/dwm/config.def.h @@ -0,0 +1,125 @@ +/* See LICENSE file for copyright and license details. */ + +/* 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 */ +static const char *fonts[] = { "monospace:size=10" }; +static const char dmenufont[] = "monospace:size=10"; +static const char col_gray1[] = "#222222"; +static const char col_gray2[] = "#444444"; +static const char col_gray3[] = "#bbbbbb"; +static const char col_gray4[] = "#eeeeee"; +static const char col_cyan[] = "#c27ba0"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +//static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +static const char *tags[] = { "dev", "www", "sys", "mus", "chat", "misc" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; +static const char *dmenucmd[] = { "dmenu_run", "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +//static const char *dmenucmd[] = { "dmenu_run", "-c", "-l", "15", NULL}; + + +static const char *termcmd[] = { "st", NULL }; + +static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_d, spawn, {.v = dmenucmd } }, + { MODKEY , 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_p, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY|ShiftMask, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_q, 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 } }, + { MODKEY, XK_minus, setgaps, {.i = -1 } }, + { MODKEY, XK_equal, setgaps, {.i = +1 } }, + { MODKEY|ShiftMask, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_x, quit, {0} }, + { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/dwm/config.h b/dwm/config.h new file mode 100644 index 0000000..7895c1e --- /dev/null +++ b/dwm/config.h @@ -0,0 +1,124 @@ +/* See LICENSE file for copyright and license details. */ + +/* 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 */ +static const char *fonts[] = { "monospace:size=10" }; +static const char dmenufont[] = "monospace:size=10"; +static const char col_gray1[] = "#222222"; +static const char col_gray2[] = "#444444"; +static const char col_gray3[] = "#bbbbbb"; +static const char col_gray4[] = "#eeeeee"; +static const char col_cyan[] = "#c27ba0"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +//static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +static const char *tags[] = { "dev", "www", "sys", "mus", "chat", "misc" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static const char *dmenucmd[] = { "dmenu_run", "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +//static const char *dmenucmd[] = { "dmenu_run", "-c", "-l", "15", NULL}; + + +static const char *termcmd[] = { "st", NULL }; + +static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_d, spawn, {.v = dmenucmd } }, + { MODKEY , 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_p, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY|ShiftMask, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_q, 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 } }, + { MODKEY, XK_minus, setgaps, {.i = -1 } }, + { MODKEY, XK_equal, setgaps, {.i = +1 } }, + { MODKEY|ShiftMask, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_x, quit, {0} }, + { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/dwm/config.mk b/dwm/config.mk new file mode 100644 index 0000000..81c493e --- /dev/null +++ b/dwm/config.mk @@ -0,0 +1,39 @@ +# dwm version +VERSION = 6.3 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = ${X11INC}/freetype2 +#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/dwm/drw.c b/dwm/drw.c new file mode 100644 index 0000000..ced7d37 --- /dev/null +++ b/dwm/drw.c @@ -0,0 +1,464 @@ +/* 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."); + } + + /* Do not allow using color fonts. This is a workaround for a BadLength + * error from Xft with color glyphs. Modelled on the Xterm workaround. See + * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + * https://lists.suckless.org/dev/1701/30932.html + * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 + * and lots more all over the internet. + */ + FcBool iscol; + if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { + XftFontClose(drw->dpy, xfont); + return NULL; + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + 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); + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + 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/dwm/drw.h b/dwm/drw.h new file mode 100644 index 0000000..6471431 --- /dev/null +++ b/dwm/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, 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/dwm/drw.o b/dwm/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..84d0b323d51f79b4c67d407d2cabc57841a19e66 GIT binary patch literal 11472 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
  • T3$ay>oK0b+^jY;%0CCPVd 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 +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/dwm/dwm-fullgaps-20200508-7b77734.diff b/dwm/dwm-fullgaps-20200508-7b77734.diff new file mode 100644 index 0000000..368c871 --- /dev/null +++ b/dwm/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/dwm/dwm-preserveonrestart-6.3.diff b/dwm/dwm-preserveonrestart-6.3.diff new file mode 100644 index 0000000..079cf05 --- /dev/null +++ b/dwm/dwm-preserveonrestart-6.3.diff @@ -0,0 +1,118 @@ +From 713fa8650f5a20006451ebcccf57a4512e83bae8 Mon Sep 17 00:00:00 2001 +From: Arda Atci +Date: Wed, 18 May 2022 17:23:16 +0300 +Subject: [PATCH] preserve clients on old tags when renewing dwm + +By default, when dwm is recompiled-restarted all clients will +lose it's current tag and collapse to first tag. This patch preserves +clients on old tags, however note that layout order is not preserved. + +--- + dwm.c | 38 +++++++++++++++++++++++++++++++++++++- + 1 file changed, 37 insertions(+), 1 deletion(-) + +diff --git a/dwm.c b/dwm.c +index a96f33c..a12e0bd 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -62,7 +62,7 @@ enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ + enum { SchemeNorm, SchemeSel }; /* color schemes */ + enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, +- NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ ++ NetWMWindowTypeDialog, NetClientList, NetClientInfo, NetLast }; /* EWMH atoms */ + enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ + enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ +@@ -198,6 +198,7 @@ static void scan(void); + static int sendevent(Client *c, Atom proto); + static void sendmon(Client *c, Monitor *m); + static void setclientstate(Client *c, long state); ++static void setclienttagprop(Client *c); + static void setfocus(Client *c); + static void setfullscreen(Client *c, int fullscreen); + static void setlayout(const Arg *arg); +@@ -1060,6 +1061,26 @@ manage(Window w, XWindowAttributes *wa) + updatewindowtype(c); + updatesizehints(c); + updatewmhints(c); ++ { ++ int format; ++ unsigned long *data, n, extra; ++ Monitor *m; ++ Atom atom; ++ if (XGetWindowProperty(dpy, c->win, netatom[NetClientInfo], 0L, 2L, False, XA_CARDINAL, ++ &atom, &format, &n, &extra, (unsigned char **)&data) == Success && n == 2) { ++ c->tags = *data; ++ for (m = mons; m; m = m->next) { ++ if (m->num == *(data+1)) { ++ c->mon = m; ++ break; ++ } ++ } ++ } ++ if (n > 0) ++ XFree(data); ++ } ++ setclienttagprop(c); ++ + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + if (!c->isfloating) +@@ -1423,6 +1444,7 @@ sendmon(Client *c, Monitor *m) + c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ + attach(c); + attachstack(c); ++ setclienttagprop(c); + focus(NULL); + arrange(NULL); + } +@@ -1566,6 +1588,7 @@ setup(void) + 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); ++ netatom[NetClientInfo] = XInternAtom(dpy, "_NET_CLIENT_INFO", False); + /* init cursors */ + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); +@@ -1589,6 +1612,7 @@ setup(void) + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); ++ XDeleteProperty(dpy, root, netatom[NetClientInfo]); + /* select events */ + wa.cursor = cursor[CurNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask +@@ -1656,11 +1680,22 @@ spawn(const Arg *arg) + } + } + ++void ++setclienttagprop(Client *c) ++{ ++ long data[] = { (long) c->tags, (long) c->mon->num }; ++ XChangeProperty(dpy, c->win, netatom[NetClientInfo], XA_CARDINAL, 32, ++ PropModeReplace, (unsigned char *) data, 2); ++} ++ + void + tag(const Arg *arg) + { ++ Client *c; + if (selmon->sel && arg->ui & TAGMASK) { ++ c = selmon->sel; + selmon->sel->tags = arg->ui & TAGMASK; ++ setclienttagprop(c); + focus(NULL); + arrange(selmon); + } +@@ -1735,6 +1770,7 @@ toggletag(const Arg *arg) + newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + if (newtags) { + selmon->sel->tags = newtags; ++ setclienttagprop(selmon->sel); + focus(NULL); + arrange(selmon); + } +-- +2.36.1 diff --git a/dwm/dwm-restartsig-20180523-6.2.diff b/dwm/dwm-restartsig-20180523-6.2.diff new file mode 100644 index 0000000..f1f8680 --- /dev/null +++ b/dwm/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/dwm/dwm.1 b/dwm/dwm.1 new file mode 100644 index 0000000..8466ea1 --- /dev/null +++ b/dwm/dwm.1 @@ -0,0 +1,219 @@ +.TH DWM 1 dwm\-VERSION +.SH NAME +dwm \- dynamic window manager +.SH SYNOPSIS +.B dwm +.RB [ \-v ] +.SH DESCRIPTION +dwm is a dynamic window manager for X. It manages windows in tiled, monocle +and floating layouts. Either layout can be applied dynamically, optimising the +environment for the application in use and the task performed. +.P +In tiled layouts windows are managed in a master and stacking area. The master +area on the left contains one window by default, and the stacking area on the +right contains all other windows. The number of master area windows can be +adjusted from zero to an arbitrary number. In monocle layout all windows are +maximised to the screen size. In floating layout windows can be resized and +moved freely. Dialog windows are always managed floating, regardless of the +layout applied. +.P +Windows are grouped by tags. Each window can be tagged with one or multiple +tags. Selecting certain tags displays all windows with these tags. +.P +Each screen contains a small status bar which displays all available tags, the +layout, the title of the focused window, and the text read from the root window +name property, if the screen is focused. A floating window is indicated with an +empty square and a maximised floating window is indicated with a filled square +before the windows title. The selected tags are indicated with a different +color. The tags of the focused window are indicated with a filled square in the +top left corner. The tags which are applied to one or more windows are +indicated with an empty square in the top left corner. +.P +dwm draws a small border around windows to indicate the focus state. +.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 +prints version information to stderr, then exits. +.SH USAGE +.SS Status bar +.TP +.B X root window name +is read and displayed in the status text area. It can be set with the +.BR xsetroot (1) +command. +.TP +.B Button1 +click on a tag label to display all windows with that tag, click on the layout +label toggles between tiled and floating layout. +.TP +.B Button3 +click on a tag label adds/removes all windows with that tag to/from the view. +.TP +.B Mod1\-Button1 +click on a tag label applies that tag to the focused window. +.TP +.B Mod1\-Button3 +click on a tag label adds/removes that tag to/from the focused window. +.SS Keyboard commands +.TP +.B Mod1\-Shift\-Return +Start +.BR st(1). +.TP +.B Mod1\-p +Spawn +.BR dmenu(1) +for launching other programs. +.TP +.B Mod1\-, +Focus previous screen, if any. +.TP +.B Mod1\-. +Focus next screen, if any. +.TP +.B Mod1\-Shift\-, +Send focused window to previous screen, if any. +.TP +.B Mod1\-Shift\-. +Send focused window to next screen, if any. +.TP +.B Mod1\-b +Toggles bar on and off. +.TP +.B Mod1\-t +Sets tiled layout. +.TP +.B Mod1\-f +Sets floating layout. +.TP +.B Mod1\-m +Sets monocle layout. +.TP +.B Mod1\-space +Toggles between current and previous layout. +.TP +.B Mod1\-j +Focus next window. +.TP +.B Mod1\-k +Focus previous window. +.TP +.B Mod1\-i +Increase number of windows in master area. +.TP +.B Mod1\-d +Decrease number of windows in master area. +.TP +.B Mod1\-l +Increase master area size. +.TP +.B Mod1\-h +Decrease master area size. +.TP +.B Mod1\-Return +Zooms/cycles focused window to/from master area (tiled layouts only). +.TP +.B Mod1\-Shift\-c +Close focused window. +.TP +.B Mod1\-Shift\-space +Toggle focused window between tiled and floating state. +.TP +.B Mod1\-Tab +Toggles to the previously selected tags. +.TP +.B Mod1\-Shift\-[1..n] +Apply nth tag to focused window. +.TP +.B Mod1\-Shift\-0 +Apply all tags to focused window. +.TP +.B Mod1\-Control\-Shift\-[1..n] +Add/remove nth tag to/from focused window. +.TP +.B Mod1\-[1..n] +View all windows with nth tag. +.TP +.B Mod1\-0 +View all windows with any tag. +.TP +.B Mod1\-Control\-[1..n] +Add/remove all windows with nth tag to/from the view. +.TP +.B Mod1\-- +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. +.TP +.B Mod1\-Control\-Shift\-q +Restart dwm. +.SS Mouse commands +.TP +.B Mod1\-Button1 +Move focused window while dragging. Tiled windows will be toggled to the floating state. +.TP +.B Mod1\-Button2 +Toggles focused window between floating and tiled state. +.TP +.B Mod1\-Button3 +Resize focused window while dragging. Tiled windows will be toggled to the floating state. +.SH 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. +.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) +.SH ISSUES +Java applications which use the XToolkit/XAWT backend may draw grey windows +only. The XToolkit/XAWT backend breaks ICCCM-compliance in recent JDK 1.5 and early +JDK 1.6 versions, because it assumes a reparenting window manager. Possible workarounds +are using JDK 1.4 (which doesn't contain the XToolkit/XAWT backend) or setting the +environment variable +.BR AWT_TOOLKIT=MToolkit +(to use the older Motif backend instead) or running +.B xprop -root -f _NET_WM_NAME 32a -set _NET_WM_NAME LG3D +or +.B wmname LG3D +(to pretend that a non-reparenting window manager is running that the +XToolkit/XAWT backend can recognize) or when using OpenJDK setting the environment variable +.BR _JAVA_AWT_WM_NONREPARENTING=1 . +.SH BUGS +Send all bug reports with a patch to hackers@suckless.org. diff --git a/dwm/dwm.1.orig b/dwm/dwm.1.orig new file mode 100644 index 0000000..192b7bd --- /dev/null +++ b/dwm/dwm.1.orig @@ -0,0 +1,196 @@ +.TH DWM 1 dwm\-VERSION +.SH NAME +dwm \- dynamic window manager +.SH SYNOPSIS +.B dwm +.RB [ \-v ] +.SH DESCRIPTION +dwm is a dynamic window manager for X. It manages windows in tiled, monocle +and floating layouts. Either layout can be applied dynamically, optimising the +environment for the application in use and the task performed. +.P +In tiled layouts windows are managed in a master and stacking area. The master +area on the left contains one window by default, and the stacking area on the +right contains all other windows. The number of master area windows can be +adjusted from zero to an arbitrary number. In monocle layout all windows are +maximised to the screen size. In floating layout windows can be resized and +moved freely. Dialog windows are always managed floating, regardless of the +layout applied. +.P +Windows are grouped by tags. Each window can be tagged with one or multiple +tags. Selecting certain tags displays all windows with these tags. +.P +Each screen contains a small status bar which displays all available tags, the +layout, the title of the focused window, and the text read from the root window +name property, if the screen is focused. A floating window is indicated with an +empty square and a maximised floating window is indicated with a filled square +before the windows title. The selected tags are indicated with a different +color. The tags of the focused window are indicated with a filled square in the +top left corner. The tags which are applied to one or more windows are +indicated with an empty square in the top left corner. +.P +dwm draws a small border around windows to indicate the focus state. +.SH OPTIONS +.TP +.B \-v +prints version information to stderr, then exits. +.SH USAGE +.SS Status bar +.TP +.B X root window name +is read and displayed in the status text area. It can be set with the +.BR xsetroot (1) +command. +.TP +.B Button1 +click on a tag label to display all windows with that tag, click on the layout +label toggles between tiled and floating layout. +.TP +.B Button3 +click on a tag label adds/removes all windows with that tag to/from the view. +.TP +.B Mod1\-Button1 +click on a tag label applies that tag to the focused window. +.TP +.B Mod1\-Button3 +click on a tag label adds/removes that tag to/from the focused window. +.SS Keyboard commands +.TP +.B Mod1\-Shift\-Return +Start +.BR st(1). +.TP +.B Mod1\-p +Spawn +.BR dmenu(1) +for launching other programs. +.TP +.B Mod1\-, +Focus previous screen, if any. +.TP +.B Mod1\-. +Focus next screen, if any. +.TP +.B Mod1\-Shift\-, +Send focused window to previous screen, if any. +.TP +.B Mod1\-Shift\-. +Send focused window to next screen, if any. +.TP +.B Mod1\-b +Toggles bar on and off. +.TP +.B Mod1\-t +Sets tiled layout. +.TP +.B Mod1\-f +Sets floating layout. +.TP +.B Mod1\-m +Sets monocle layout. +.TP +.B Mod1\-space +Toggles between current and previous layout. +.TP +.B Mod1\-j +Focus next window. +.TP +.B Mod1\-k +Focus previous window. +.TP +.B Mod1\-i +Increase number of windows in master area. +.TP +.B Mod1\-d +Decrease number of windows in master area. +.TP +.B Mod1\-l +Increase master area size. +.TP +.B Mod1\-h +Decrease master area size. +.TP +.B Mod1\-Return +Zooms/cycles focused window to/from master area (tiled layouts only). +.TP +.B Mod1\-Shift\-c +Close focused window. +.TP +.B Mod1\-Shift\-space +Toggle focused window between tiled and floating state. +.TP +.B Mod1\-Tab +Toggles to the previously selected tags. +.TP +.B Mod1\-Shift\-[1..n] +Apply nth tag to focused window. +.TP +.B Mod1\-Shift\-0 +Apply all tags to focused window. +.TP +.B Mod1\-Control\-Shift\-[1..n] +Add/remove nth tag to/from focused window. +.TP +.B Mod1\-[1..n] +View all windows with nth tag. +.TP +.B Mod1\-0 +View all windows with any tag. +.TP +.B Mod1\-Control\-[1..n] +Add/remove all windows with nth tag to/from the view. +.TP +.B Mod1\-- +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. +.TP +.B Mod1\-Control\-Shift\-q +Restart dwm. +.SS Mouse commands +.TP +.B Mod1\-Button1 +Move focused window while dragging. Tiled windows will be toggled to the floating state. +.TP +.B Mod1\-Button2 +Toggles focused window between floating and tiled state. +.TP +.B Mod1\-Button3 +Resize focused window while dragging. Tiled windows will be toggled to the floating state. +.SH CUSTOMIZATION +dwm is customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH 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) +.SH ISSUES +Java applications which use the XToolkit/XAWT backend may draw grey windows +only. The XToolkit/XAWT backend breaks ICCCM-compliance in recent JDK 1.5 and early +JDK 1.6 versions, because it assumes a reparenting window manager. Possible workarounds +are using JDK 1.4 (which doesn't contain the XToolkit/XAWT backend) or setting the +environment variable +.BR AWT_TOOLKIT=MToolkit +(to use the older Motif backend instead) or running +.B xprop -root -f _NET_WM_NAME 32a -set _NET_WM_NAME LG3D +or +.B wmname LG3D +(to pretend that a non-reparenting window manager is running that the +XToolkit/XAWT backend can recognize) or when using OpenJDK setting the environment variable +.BR _JAVA_AWT_WM_NONREPARENTING=1 . +.SH BUGS +Send all bug reports with a patch to hackers@suckless.org. diff --git a/dwm/dwm.c b/dwm/dwm.c new file mode 100644 index 0000000..f4944d0 --- /dev/null +++ b/dwm/dwm.c @@ -0,0 +1,2302 @@ +/* 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 +#ifdef XINERAMA +#include +#endif /* XINERAMA */ +#include + +#include "drw.h" +#include "util.h" + +/* macros */ +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) +#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define MOUSEMASK (BUTTONMASK|PointerMotionMask) +#define WIDTH(X) ((X)->w + 2 * (X)->bw) +#define HEIGHT(X) ((X)->h + 2 * (X)->bw) +#define TAGMASK ((1 << LENGTH(tags)) - 1) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +enum { SchemeNorm, SchemeSel }; /* color schemes */ +enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetClientInfo, NetLast }; /* EWMH atoms */ +enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ +enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ + +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mask; + unsigned int button; + void (*func)(const Arg *arg); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct Client Client; +struct Client { + char name[256]; + float mina, maxa; + int x, y, w, h; + int oldx, oldy, oldw, oldh; + int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid; + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; + Client *next; + Client *snext; + Monitor *mon; + Window win; +}; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +struct Monitor { + char ltsymbol[16]; + float mfact; + int nmaster; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; + int showbar; + int topbar; + Client *clients; + Client *sel; + Client *stack; + Monitor *next; + Window barwin; + const Layout *lt[2]; +}; + +typedef struct { + const char *class; + const char *instance; + const char *title; + unsigned int tags; + int isfloating; + int monitor; +} Rule; + +/* function declarations */ +static void applyrules(Client *c); +static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +static void arrange(Monitor *m); +static void arrangemon(Monitor *m); +static void attach(Client *c); +static void attachstack(Client *c); +static void buttonpress(XEvent *e); +static void checkotherwm(void); +static void cleanup(void); +static void cleanupmon(Monitor *mon); +static void clientmessage(XEvent *e); +static void configure(Client *c); +static void configurenotify(XEvent *e); +static void configurerequest(XEvent *e); +static Monitor *createmon(void); +static void destroynotify(XEvent *e); +static void detach(Client *c); +static void detachstack(Client *c); +static Monitor *dirtomon(int dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static void enternotify(XEvent *e); +static void expose(XEvent *e); +static void focus(Client *c); +static void focusin(XEvent *e); +static void focusmon(const Arg *arg); +static void focusstack(const Arg *arg); +static Atom getatomprop(Client *c, Atom prop); +static int getrootptr(int *x, int *y); +static long getstate(Window w); +static int gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void grabbuttons(Client *c, int focused); +static void grabkeys(void); +static void incnmaster(const Arg *arg); +static void keypress(XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window w, XWindowAttributes *wa); +static void mappingnotify(XEvent *e); +static void maprequest(XEvent *e); +static void monocle(Monitor *m); +static void motionnotify(XEvent *e); +static void movemouse(const Arg *arg); +static Client *nexttiled(Client *c); +static void pop(Client *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 resize(Client *c, int x, int y, int w, int h, int interact); +static void resizeclient(Client *c, int x, int y, int w, int h); +static void resizemouse(const Arg *arg); +static void restack(Monitor *m); +static void run(void); +static void runautostart(void); +static void scan(void); +static int sendevent(Client *c, Atom proto); +static void sendmon(Client *c, Monitor *m); +static void setclientstate(Client *c, long state); +static void setclienttagprop(Client *c); +static void setfocus(Client *c); +static void setfullscreen(Client *c, int fullscreen); +static void setgaps(const Arg *arg); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setup(void); +static void seturgent(Client *c, int urg); +static void showhide(Client *c); +static void sigchld(int unused); +static void 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); +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 unfocus(Client *c, int setfocus); +static void unmanage(Client *c, int destroyed); +static void unmapnotify(XEvent *e); +static void updatebarpos(Monitor *m); +static void updatebars(void); +static void updateclientlist(void); +static int updategeom(void); +static void updatenumlockmask(void); +static void updatesizehints(Client *c); +static void updatestatus(void); +static void updatetitle(Client *c); +static void updatewindowtype(Client *c); +static void updatewmhints(Client *c); +static void view(const Arg *arg); +static Client *wintoclient(Window w); +static Monitor *wintomon(Window w); +static int xerror(Display *dpy, XErrorEvent *ee); +static int xerrordummy(Display *dpy, XErrorEvent *ee); +static int xerrorstart(Display *dpy, XErrorEvent *ee); +static void zoom(const Arg *arg); + +/* variables */ +static const char 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 */ +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, + [UnmapNotify] = unmapnotify +}; +static Atom wmatom[WMLast], netatom[NetLast]; +static int restart = 0; +static int running = 1; +static Cur *cursor[CurLast]; +static Clr **scheme; +static Display *dpy; +static Drw *drw; +static Monitor *mons, *selmon; +static Window root, wmcheckwin; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* compile-time check if all tags fit into an unsigned int bit array. */ +struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; + +/* function implementations */ +void +applyrules(Client *c) +{ + const char *class, *instance; + unsigned int i; + const Rule *r; + Monitor *m; + XClassHint ch = { NULL, NULL }; + + /* rule matching */ + c->isfloating = 0; + c->tags = 0; + XGetClassHint(dpy, c->win, &ch); + class = ch.res_class ? ch.res_class : broken; + instance = ch.res_name ? ch.res_name : broken; + + for (i = 0; i < LENGTH(rules); i++) { + r = &rules[i]; + if ((!r->title || strstr(c->name, r->title)) + && (!r->class || strstr(class, r->class)) + && (!r->instance || strstr(instance, r->instance))) + { + c->isfloating = r->isfloating; + c->tags |= r->tags; + for (m = mons; m && m->num != r->monitor; m = m->next); + if (m) + c->mon = m; + } + } + if (ch.res_class) + XFree(ch.res_class); + if (ch.res_name) + XFree(ch.res_name); + c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags]; +} + +int +applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) +{ + int baseismin; + Monitor *m = c->mon; + + /* set minimum possible */ + *w = MAX(1, *w); + *h = MAX(1, *h); + if (interact) { + if (*x > sw) + *x = sw - WIDTH(c); + if (*y > sh) + *y = sh - HEIGHT(c); + if (*x + *w + 2 * c->bw < 0) + *x = 0; + if (*y + *h + 2 * c->bw < 0) + *y = 0; + } else { + if (*x >= m->wx + m->ww) + *x = m->wx + m->ww - WIDTH(c); + if (*y >= m->wy + m->wh) + *y = m->wy + m->wh - HEIGHT(c); + if (*x + *w + 2 * c->bw <= m->wx) + *x = m->wx; + if (*y + *h + 2 * c->bw <= m->wy) + *y = m->wy; + } + if (*h < bh) + *h = bh; + if (*w < bh) + *w = bh; + if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { + 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)) + click = ClkStatusText; + else + click = ClkWinTitle; + } else if ((c = wintoclient(ev->window))) { + focus(c); + restack(selmon); + XAllowEvents(dpy, ReplayPointer, CurrentTime); + click = ClkClientWin; + } + for (i = 0; i < LENGTH(buttons); i++) + if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button + && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) + buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); +} + +void +checkotherwm(void) +{ + xerrorxlib = XSetErrorHandler(xerrorstart); + /* this causes an error if some other window manager is running */ + XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); + XSync(dpy, False); + XSetErrorHandler(xerror); + XSync(dpy, False); +} + +void +cleanup(void) +{ + Arg a = {.ui = ~0}; + Layout foo = { "", NULL }; + Monitor *m; + size_t i; + + view(&a); + selmon->lt[selmon->sellt] = &foo; + for (m = mons; m; m = m->next) + while (m->stack) + unmanage(m->stack, 0); + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); + for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); + for (i = 0; i < LENGTH(colors); i++) + free(scheme[i]); + 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) +{ + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { + if (cme->data.l[1] == netatom[NetWMFullscreen] + || cme->data.l[2] == netatom[NetWMFullscreen]) + setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ + || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); + } else if (cme->message_type == netatom[NetActiveWindow]) { + if (c != selmon->sel && !c->isurgent) + seturgent(c, 1); + } +} + +void +configure(Client *c) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = c->win; + ce.window = c->win; + ce.x = c->x; + ce.y = c->y; + ce.width = c->w; + ce.height = c->h; + ce.border_width = c->bw; + ce.above = None; + ce.override_redirect = False; + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); +} + +void +configurenotify(XEvent *e) +{ + Monitor *m; + Client *c; + XConfigureEvent *ev = &e->xconfigure; + int dirty; + + /* TODO: updategeom handling sucks, needs to be simplified */ + if (ev->window == root) { + dirty = (sw != ev->width || sh != ev->height); + sw = ev->width; + sh = ev->height; + if (updategeom() || dirty) { + drw_resize(drw, sw, bh); + updatebars(); + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); + XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); + } + focus(NULL); + arrange(NULL); + } + } +} + +void +configurerequest(XEvent *e) +{ + Client *c; + Monitor *m; + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + + if ((c = wintoclient(ev->window))) { + if (ev->value_mask & CWBorderWidth) + c->bw = ev->border_width; + else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { + m = c->mon; + if (ev->value_mask & CWX) { + c->oldx = c->x; + c->x = m->mx + ev->x; + } + if (ev->value_mask & CWY) { + c->oldy = c->y; + c->y = m->my + ev->y; + } + if (ev->value_mask & CWWidth) { + c->oldw = c->w; + c->w = ev->width; + } + if (ev->value_mask & CWHeight) { + c->oldh = c->h; + c->h = ev->height; + } + if ((c->x + c->w) > m->mx + m->mw && c->isfloating) + c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */ + if ((c->y + c->h) > m->my + m->mh && c->isfloating) + c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ + if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) + configure(c); + if (ISVISIBLE(c)) + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + } else + configure(c); + } else { + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + } + XSync(dpy, False); +} + +Monitor * +createmon(void) +{ + Monitor *m; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; + m->mfact = mfact; + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; + m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + return m; +} + +void +destroynotify(XEvent *e) +{ + Client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); +} + +void +detach(Client *c) +{ + Client **tc; + + for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); + *tc = c->next; +} + +void +detachstack(Client *c) +{ + Client **tc, *t; + + for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); + *tc = c->snext; + + if (c == c->mon->sel) { + for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); + c->mon->sel = t; + } +} + +Monitor * +dirtomon(int dir) +{ + Monitor *m = NULL; + + if (dir > 0) { + if (!(m = selmon->next)) + m = mons; + } else if (selmon == mons) + for (m = mons; m->next; m = m->next); + else + for (m = mons; m->next != selmon; m = m->next); + return m; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + unsigned int i, occ = 0, urg = 0; + Client *c; + + if (!m->showbar) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drw_setscheme(drw, scheme[SchemeNorm]); + tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ + drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); + } + + for (c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(tags[i]); + drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drw_rect(drw, x + boxs, boxs, boxw, boxw, + m == selmon && selmon->sel && selmon->sel->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m->ltsymbol); + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + + if ((w = m->ww - tw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); + if (m->sel->isfloating) + drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } + drw_map(drw, m->barwin, 0, 0, m->ww, bh); +} + +void +drawbars(void) +{ + Monitor *m; + + for (m = mons; m; m = m->next) + drawbar(m); +} + +void +enternotify(XEvent *e) +{ + Client *c; + Monitor *m; + XCrossingEvent *ev = &e->xcrossing; + + if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) + return; + c = wintoclient(ev->window); + m = c ? c->mon : wintomon(ev->window); + if (m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + } else if (!c || c == selmon->sel) + return; + focus(c); +} + +void +expose(XEvent *e) +{ + Monitor *m; + XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && (m = wintomon(ev->window))) + drawbar(m); +} + +void +focus(Client *c) +{ + if (!c || !ISVISIBLE(c)) + for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); + if (selmon->sel && selmon->sel != c) + unfocus(selmon->sel, 0); + if (c) { + if (c->mon != selmon) + selmon = c->mon; + if (c->isurgent) + seturgent(c, 0); + detachstack(c); + attachstack(c); + grabbuttons(c, 1); + XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); + setfocus(c); + } else { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } + selmon->sel = c; + drawbars(); +} + +/* there are some broken focus acquiring clients needing extra handling */ +void +focusin(XEvent *e) +{ + XFocusChangeEvent *ev = &e->xfocus; + + if (selmon->sel && ev->window != selmon->sel->win) + setfocus(selmon->sel); +} + +void +focusmon(const Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + if ((m = dirtomon(arg->i)) == selmon) + return; + unfocus(selmon->sel, 0); + selmon = m; + focus(NULL); +} + +void +focusstack(const Arg *arg) +{ + Client *c = NULL, *i; + + if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen)) + return; + if (arg->i > 0) { + for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + if (!c) + for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + } else { + for (i = selmon->clients; i != selmon->sel; i = i->next) + if (ISVISIBLE(i)) + c = i; + if (!c) + for (; i; i = i->next) + if (ISVISIBLE(i)) + c = i; + } + if (c) { + focus(c); + restack(selmon); + } +} + +Atom +getatomprop(Client *c, Atom prop) +{ + int di; + unsigned long dl; + unsigned char *p = NULL; + Atom da, atom = None; + + if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; + XFree(p); + } + return atom; +} + +int +getrootptr(int *x, int *y) +{ + int di; + unsigned int dui; + Window dummy; + + return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); +} + +long +getstate(Window w) +{ + int format; + long result = -1; + unsigned char *p = NULL; + unsigned long n, extra; + Atom real; + + if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return -1; + if (n != 0) + result = *p; + XFree(p); + return result; +} + +int +gettextprop(Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return 0; + text[0] = '\0'; + if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) + return 0; + if (name.encoding == XA_STRING) { + strncpy(text, (char *)name.value, size - 1); + } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + text[size - 1] = '\0'; + XFree(name.value); + return 1; +} + +void +grabbuttons(Client *c, int focused) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + if (!focused) + XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, + BUTTONMASK, GrabModeSync, GrabModeSync, None, None); + for (i = 0; i < LENGTH(buttons); i++) + if (buttons[i].click == ClkClientWin) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabButton(dpy, buttons[i].button, + buttons[i].mask | modifiers[j], + c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + } +} + +void +grabkeys(void) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode code; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < LENGTH(keys); i++) + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, code, keys[i].mod | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); + } +} + +void +incnmaster(const Arg *arg) +{ + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +#ifdef XINERAMA +static int +isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) +{ + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return 0; + return 1; +} +#endif /* XINERAMA */ + +void +keypress(XEvent *e) +{ + unsigned int i; + KeySym keysym; + XKeyEvent *ev; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); + for (i = 0; i < LENGTH(keys); i++) + if (keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); +} + +void +killclient(const Arg *arg) +{ + if (!selmon->sel) + return; + if (!sendevent(selmon->sel, wmatom[WMDelete])) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); + XKillClient(dpy, selmon->sel->win); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } +} + +void +manage(Window w, XWindowAttributes *wa) +{ + Client *c, *t = NULL; + Window trans = None; + XWindowChanges wc; + + c = ecalloc(1, sizeof(Client)); + c->win = w; + /* geometry */ + c->x = c->oldx = wa->x; + c->y = c->oldy = wa->y; + c->w = c->oldw = wa->width; + c->h = c->oldh = wa->height; + c->oldbw = wa->border_width; + + updatetitle(c); + if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { + c->mon = t->mon; + c->tags = t->tags; + } else { + c->mon = selmon; + applyrules(c); + } + + if (c->x + WIDTH(c) > c->mon->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); + { + int format; + unsigned long *data, n, extra; + Monitor *m; + Atom atom; + if (XGetWindowProperty(dpy, c->win, netatom[NetClientInfo], 0L, 2L, False, XA_CARDINAL, + &atom, &format, &n, &extra, (unsigned char **)&data) == Success && n == 2) { + c->tags = *data; + for (m = mons; m; m = m->next) { + if (m->num == *(data+1)) { + c->mon = m; + break; + } + } + } + if (n > 0) + XFree(data); + } + setclienttagprop(c); + + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + if (!c->isfloating) + c->isfloating = c->oldstate = trans != None || c->isfixed; + if (c->isfloating) + XRaiseWindow(dpy, c->win); + attach(c); + attachstack(c); + XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); + XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ + setclientstate(c, NormalState); + if (c->mon == selmon) + unfocus(selmon->sel, 0); + c->mon->sel = c; + arrange(c->mon); + XMapWindow(dpy, c->win); + focus(NULL); +} + +void +mappingnotify(XEvent *e) +{ + XMappingEvent *ev = &e->xmapping; + + XRefreshKeyboardMapping(ev); + if (ev->request == MappingKeyboard) + grabkeys(); +} + +void +maprequest(XEvent *e) +{ + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + + if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redirect) + return; + if (!wintoclient(ev->window)) + manage(ev->window, &wa); +} + +void +monocle(Monitor *m) +{ + unsigned int n = 0; + Client *c; + + for (c = m->clients; c; c = c->next) + if (ISVISIBLE(c)) + n++; + if (n > 0) /* override layout symbol */ + snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); + for (c = nexttiled(m->clients); c; c = nexttiled(c->next)) + resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0); +} + +void +motionnotify(XEvent *e) +{ + static Monitor *mon = NULL; + Monitor *m; + XMotionEvent *ev = &e->xmotion; + + if (ev->window != root) + return; + if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + mon = m; +} + +void +movemouse(const Arg *arg) +{ + int x, y, ocx, ocy, nx, ny; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess) + return; + if (!getrootptr(&x, &y)) + return; + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nx = ocx + (ev.xmotion.x - x); + ny = ocy + (ev.xmotion.y - y); + if (abs(selmon->wx - nx) < snap) + nx = selmon->wx; + else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap) + nx = selmon->wx + selmon->ww - WIDTH(c); + if (abs(selmon->wy - ny) < snap) + ny = selmon->wy; + else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap) + ny = selmon->wy + selmon->wh - HEIGHT(c); + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) + togglefloating(NULL); + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, nx, ny, c->w, c->h, 1); + break; + } + } while (ev.type != ButtonRelease); + XUngrabPointer(dpy, CurrentTime); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +Client * +nexttiled(Client *c) +{ + for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); + return c; +} + +void +pop(Client *c) +{ + detach(c); + attach(c); + focus(c); + arrange(c->mon); +} + +void +propertynotify(XEvent *e) +{ + Client *c; + Window trans; + XPropertyEvent *ev = &e->xproperty; + + if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ + else if ((c = wintoclient(ev->window))) { + switch(ev->atom) { + default: break; + case XA_WM_TRANSIENT_FOR: + if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && + (c->isfloating = (wintoclient(trans)) != NULL)) + arrange(c->mon); + break; + case XA_WM_NORMAL_HINTS: + 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); + } + 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 +resize(Client *c, int x, int y, int w, int h, int interact) +{ + if (applysizehints(c, &x, &y, &w, &h, interact)) + resizeclient(c, x, y, w, h); +} + +void +resizeclient(Client *c, int x, int y, int w, int h) +{ + XWindowChanges wc; + + c->oldx = c->x; c->x = wc.x = x; + c->oldy = c->y; c->y = wc.y = y; + c->oldw = c->w; c->w = wc.width = w; + c->oldh = c->h; c->h = wc.height = h; + wc.border_width = c->bw; + XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); + configure(c); + XSync(dpy, False); +} + +void +resizemouse(const Arg *arg) +{ + int ocx, ocy, nw, nh; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess) + return; + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1); + nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1); + if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww + && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh) + { + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nw - c->w) > snap || abs(nh - c->h) > snap)) + togglefloating(NULL); + } + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, c->x, c->y, nw, nh, 1); + break; + } + } while (ev.type != ButtonRelease); + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + XUngrabPointer(dpy, CurrentTime); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +void +restack(Monitor *m) +{ + Client *c; + XEvent ev; + XWindowChanges wc; + + drawbar(m); + if (!m->sel) + return; + if (m->sel->isfloating || !m->lt[m->sellt]->arrange) + XRaiseWindow(dpy, m->sel->win); + if (m->lt[m->sellt]->arrange) { + wc.stack_mode = Below; + wc.sibling = m->barwin; + for (c = m->stack; c; c = c->snext) + if (!c->isfloating && ISVISIBLE(c)) { + XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); + wc.sibling = c->win; + } + } + XSync(dpy, False); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); +} + +void +run(void) +{ + XEvent ev; + /* main event loop */ + XSync(dpy, False); + while (running && !XNextEvent(dpy, &ev)) + if (handler[ev.type]) + handler[ev.type](&ev); /* call handler */ +} + +void +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); + setclienttagprop(c); + focus(NULL); + arrange(NULL); +} + +void +setclientstate(Client *c, long state) +{ + long data[] = { state, None }; + + XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32, + PropModeReplace, (unsigned char *)data, 2); +} + +int +sendevent(Client *c, Atom proto) +{ + int n; + Atom *protocols; + int exists = 0; + XEvent ev; + + if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { + while (!exists && n--) + exists = protocols[n] == proto; + XFree(protocols); + } + if (exists) { + ev.type = ClientMessage; + ev.xclient.window = c->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = proto; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, c->win, False, NoEventMask, &ev); + } + return exists; +} + +void +setfocus(Client *c) +{ + if (!c->neverfocus) { + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XChangeProperty(dpy, root, netatom[NetActiveWindow], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(c->win), 1); + } + sendevent(c, wmatom[WMTakeFocus]); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + if (fullscreen && !c->isfullscreen) { + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); + c->isfullscreen = 1; + c->oldstate = c->isfloating; + c->oldbw = c->bw; + c->bw = 0; + c->isfloating = 1; + resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh); + XRaiseWindow(dpy, c->win); + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)0, 0); + c->isfullscreen = 0; + c->isfloating = c->oldstate; + c->bw = c->oldbw; + c->x = c->oldx; + c->y = c->oldy; + c->w = c->oldw; + c->h = c->oldh; + resizeclient(c, c->x, c->y, c->w, c->h); + arrange(c->mon); + } +} + +void +setgaps(const Arg *arg) +{ + if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) + selmon->gappx = 0; + else + selmon->gappx += arg->i; + arrange(selmon); +} + +void +setlayout(const Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); + if (selmon->sel) + arrange(selmon); + else + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + if (f < 0.05 || f > 0.95) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + + /* clean up any zombies immediately */ + sigchld(0); + + 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 + 2; + updategeom(); + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + netatom[NetClientInfo] = XInternAtom(dpy, "_NET_CLIENT_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 *)); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); + /* init bars */ + updatebars(); + updatestatus(); + /* supporting window for NetWMCheck */ + wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8, + PropModeReplace, (unsigned char *) "dwm", 3); + XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + /* EWMH support per view */ + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); + XDeleteProperty(dpy, root, netatom[NetClientInfo]); + /* select events */ + wa.cursor = cursor[CurNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + |ButtonPressMask|PointerMotionMask|EnterWindowMask + |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; + XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(NULL); +} + +void +seturgent(Client *c, int urg) +{ + XWMHints *wmh; + + c->isurgent = urg; + if (!(wmh = XGetWMHints(dpy, c->win))) + return; + wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint); + XSetWMHints(dpy, c->win, wmh); + XFree(wmh); +} + +void +showhide(Client *c) +{ + if (!c) + return; + if (ISVISIBLE(c)) { + /* show clients top down */ + XMoveWindow(dpy, c->win, c->x, c->y); + if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen) + resize(c, c->x, c->y, c->w, c->h, 0); + showhide(c->snext); + } else { + /* hide clients bottom up */ + showhide(c->snext); + XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y); + } +} + +void +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("can't install SIGCHLD handler:"); + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +void +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 +setclienttagprop(Client *c) +{ + long data[] = { (long) c->tags, (long) c->mon->num }; + XChangeProperty(dpy, c->win, netatom[NetClientInfo], XA_CARDINAL, 32, + PropModeReplace, (unsigned char *) data, 2); +} + +void +tag(const Arg *arg) +{ + Client *c; + if (selmon->sel && arg->ui & TAGMASK) { + c = selmon->sel; + selmon->sel->tags = arg->ui & TAGMASK; + setclienttagprop(c); + focus(NULL); + arrange(selmon); + } +} + +void +tagmon(const Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendmon(selmon->sel, dirtomon(arg->i)); +} + +void +tile(Monitor *m) +{ + unsigned int i, n, h, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else + mw = m->ww - m->gappx; + for (i = 0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + if (i < m->nmaster) { + h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; + resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); + if (my + HEIGHT(c) + m->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; + } +} + +void +togglebar(const Arg *arg) +{ + selmon->showbar = !selmon->showbar; + updatebarpos(selmon); + XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); + arrange(selmon); +} + +void +togglefloating(const Arg *arg) +{ + if (!selmon->sel) + return; + if (selmon->sel->isfullscreen) /* no support for fullscreen windows */ + return; + selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; + if (selmon->sel->isfloating) + resize(selmon->sel, selmon->sel->x, selmon->sel->y, + selmon->sel->w, selmon->sel->h, 0); + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + unsigned int newtags; + + if (!selmon->sel) + return; + newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + if (newtags) { + selmon->sel->tags = newtags; + setclienttagprop(selmon->sel); + focus(NULL); + arrange(selmon); + } +} + +void +toggleview(const Arg *arg) +{ + unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + focus(NULL); + arrange(selmon); + } +} + +void +unfocus(Client *c, int setfocus) +{ + if (!c) + return; + grabbuttons(c, 0); + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + if (setfocus) { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } +} + +void +unmanage(Client *c, int destroyed) +{ + Monitor *m = c->mon; + XWindowChanges wc; + + detach(c); + detachstack(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + 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); + } +} + +void +updatebars(void) +{ + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, + .background_pixmap = ParentRelative, + .event_mask = ButtonPressMask|ExposureMask + }; + XClassHint ch = {"dwm", "dwm"}; + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; + m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +} + +void +updatebarpos(Monitor *m) +{ + m->wy = m->my; + m->wh = m->mh; + if (m->showbar) { + m->wh -= bh; + m->by = m->topbar ? m->wy : m->wy + m->wh; + m->wy = m->topbar ? m->wy + bh : m->wy; + } else + m->by = -bh; +} + +void +updateclientlist() +{ + Client *c; + Monitor *m; + + XDeleteProperty(dpy, root, netatom[NetClientList]); + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + XChangeProperty(dpy, root, netatom[NetClientList], + XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); +} + +int +updategeom(void) +{ + int dirty = 0; + +#ifdef XINERAMA + if (XineramaIsActive(dpy)) { + int i, j, n, nn; + Client *c; + Monitor *m; + XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn); + XineramaScreenInfo *unique = NULL; + + for (n = 0, m = mons; m; m = m->next, n++); + /* only consider unique geometries as separate screens */ + unique = ecalloc(nn, sizeof(XineramaScreenInfo)); + for (i = 0, j = 0; i < nn; i++) + if (isuniquegeom(unique, j, &info[i])) + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); + XFree(info); + nn = j; + + /* 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); +} + +void +updatetitle(Client *c) +{ + if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) + gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); + if (c->name[0] == '\0') /* hack to mark broken clients */ + strcpy(c->name, broken); +} + +void +updatewindowtype(Client *c) +{ + Atom state = getatomprop(c, netatom[NetWMState]); + Atom wtype = getatomprop(c, netatom[NetWMWindowType]); + + if (state == netatom[NetWMFullscreen]) + setfullscreen(c, 1); + if (wtype == netatom[NetWMWindowTypeDialog]) + c->isfloating = 1; +} + +void +updatewmhints(Client *c) +{ + XWMHints *wmh; + + if ((wmh = XGetWMHints(dpy, c->win))) { + if (c == selmon->sel && wmh->flags & XUrgencyHint) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, c->win, wmh); + } else + c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} + +void +view(const Arg *arg) +{ + if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); +} + +Client * +wintoclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + if (c->win == w) + return c; + return NULL; +} + +Monitor * +wintomon(Window w) +{ + int x, y; + Client *c; + Monitor *m; + + if (w == root && getrootptr(&x, &y)) + return recttomon(x, y, 1, 1); + for (m = mons; m; m = m->next) + if (w == m->barwin) + return m; + if ((c = wintoclient(w))) + return c->mon; + return selmon; +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) + return 0; + fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +int +xerrordummy(Display *dpy, XErrorEvent *ee) +{ + return 0; +} + +/* Startup Error handler to check if another window manager + * is already running. */ +int +xerrorstart(Display *dpy, XErrorEvent *ee) +{ + die("dwm: another window manager is already running"); + return -1; +} + +void +zoom(const Arg *arg) +{ + Client *c = selmon->sel; + + if (!selmon->lt[selmon->sellt]->arrange || !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/dwm/dwm.c.orig b/dwm/dwm.c.orig new file mode 100644 index 0000000..47117e7 --- /dev/null +++ b/dwm/dwm.c.orig @@ -0,0 +1,2218 @@ +/* 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 +#ifdef XINERAMA +#include +#endif /* XINERAMA */ +#include + +#include "drw.h" +#include "util.h" + +/* macros */ +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) +#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define MOUSEMASK (BUTTONMASK|PointerMotionMask) +#define WIDTH(X) ((X)->w + 2 * (X)->bw) +#define HEIGHT(X) ((X)->h + 2 * (X)->bw) +#define TAGMASK ((1 << LENGTH(tags)) - 1) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +enum { SchemeNorm, SchemeSel }; /* color schemes */ +enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetClientInfo, NetLast }; /* EWMH atoms */ +enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ +enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ + +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mask; + unsigned int button; + void (*func)(const Arg *arg); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct Client Client; +struct Client { + char name[256]; + float mina, maxa; + int x, y, w, h; + int oldx, oldy, oldw, oldh; + int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid; + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; + Client *next; + Client *snext; + Monitor *mon; + Window win; +}; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +struct Monitor { + char ltsymbol[16]; + float mfact; + int nmaster; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; + int showbar; + int topbar; + Client *clients; + Client *sel; + Client *stack; + Monitor *next; + Window barwin; + const Layout *lt[2]; +}; + +typedef struct { + const char *class; + const char *instance; + const char *title; + unsigned int tags; + int isfloating; + int monitor; +} Rule; + +/* function declarations */ +static void applyrules(Client *c); +static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +static void arrange(Monitor *m); +static void arrangemon(Monitor *m); +static void attach(Client *c); +static void attachstack(Client *c); +static void buttonpress(XEvent *e); +static void checkotherwm(void); +static void cleanup(void); +static void cleanupmon(Monitor *mon); +static void clientmessage(XEvent *e); +static void configure(Client *c); +static void configurenotify(XEvent *e); +static void configurerequest(XEvent *e); +static Monitor *createmon(void); +static void destroynotify(XEvent *e); +static void detach(Client *c); +static void detachstack(Client *c); +static Monitor *dirtomon(int dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static void enternotify(XEvent *e); +static void expose(XEvent *e); +static void focus(Client *c); +static void focusin(XEvent *e); +static void focusmon(const Arg *arg); +static void focusstack(const Arg *arg); +static Atom getatomprop(Client *c, Atom prop); +static int getrootptr(int *x, int *y); +static long getstate(Window w); +static int gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void grabbuttons(Client *c, int focused); +static void grabkeys(void); +static void incnmaster(const Arg *arg); +static void keypress(XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window w, XWindowAttributes *wa); +static void mappingnotify(XEvent *e); +static void maprequest(XEvent *e); +static void monocle(Monitor *m); +static void motionnotify(XEvent *e); +static void movemouse(const Arg *arg); +static Client *nexttiled(Client *c); +static void pop(Client *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 resize(Client *c, int x, int y, int w, int h, int interact); +static void resizeclient(Client *c, int x, int y, int w, int h); +static void resizemouse(const Arg *arg); +static void restack(Monitor *m); +static void run(void); +static void scan(void); +static int sendevent(Client *c, Atom proto); +static void sendmon(Client *c, Monitor *m); +static void setclientstate(Client *c, long state); +static void setclienttagprop(Client *c); +static void setfocus(Client *c); +static void setfullscreen(Client *c, int fullscreen); +static void setgaps(const Arg *arg); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setup(void); +static void seturgent(Client *c, int urg); +static void showhide(Client *c); +static void sigchld(int unused); +static void 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); +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 unfocus(Client *c, int setfocus); +static void unmanage(Client *c, int destroyed); +static void unmapnotify(XEvent *e); +static void updatebarpos(Monitor *m); +static void updatebars(void); +static void updateclientlist(void); +static int updategeom(void); +static void updatenumlockmask(void); +static void updatesizehints(Client *c); +static void updatestatus(void); +static void updatetitle(Client *c); +static void updatewindowtype(Client *c); +static void updatewmhints(Client *c); +static void view(const Arg *arg); +static Client *wintoclient(Window w); +static Monitor *wintomon(Window w); +static int xerror(Display *dpy, XErrorEvent *ee); +static int xerrordummy(Display *dpy, XErrorEvent *ee); +static int xerrorstart(Display *dpy, XErrorEvent *ee); +static void zoom(const Arg *arg); + +/* variables */ +static const char broken[] = "broken"; +static char stext[256]; +static int screen; +static int sw, sh; /* X display screen geometry width, height */ +static int bh; /* 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, + [UnmapNotify] = unmapnotify +}; +static Atom wmatom[WMLast], netatom[NetLast]; +static int restart = 0; +static int running = 1; +static Cur *cursor[CurLast]; +static Clr **scheme; +static Display *dpy; +static Drw *drw; +static Monitor *mons, *selmon; +static Window root, wmcheckwin; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* compile-time check if all tags fit into an unsigned int bit array. */ +struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; + +/* function implementations */ +void +applyrules(Client *c) +{ + const char *class, *instance; + unsigned int i; + const Rule *r; + Monitor *m; + XClassHint ch = { NULL, NULL }; + + /* rule matching */ + c->isfloating = 0; + c->tags = 0; + XGetClassHint(dpy, c->win, &ch); + class = ch.res_class ? ch.res_class : broken; + instance = ch.res_name ? ch.res_name : broken; + + for (i = 0; i < LENGTH(rules); i++) { + r = &rules[i]; + if ((!r->title || strstr(c->name, r->title)) + && (!r->class || strstr(class, r->class)) + && (!r->instance || strstr(instance, r->instance))) + { + c->isfloating = r->isfloating; + c->tags |= r->tags; + for (m = mons; m && m->num != r->monitor; m = m->next); + if (m) + c->mon = m; + } + } + if (ch.res_class) + XFree(ch.res_class); + if (ch.res_name) + XFree(ch.res_name); + c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags]; +} + +int +applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) +{ + int baseismin; + Monitor *m = c->mon; + + /* set minimum possible */ + *w = MAX(1, *w); + *h = MAX(1, *h); + if (interact) { + if (*x > sw) + *x = sw - WIDTH(c); + if (*y > sh) + *y = sh - HEIGHT(c); + if (*x + *w + 2 * c->bw < 0) + *x = 0; + if (*y + *h + 2 * c->bw < 0) + *y = 0; + } else { + if (*x >= m->wx + m->ww) + *x = m->wx + m->ww - WIDTH(c); + if (*y >= m->wy + m->wh) + *y = m->wy + m->wh - HEIGHT(c); + if (*x + *w + 2 * c->bw <= m->wx) + *x = m->wx; + if (*y + *h + 2 * c->bw <= m->wy) + *y = m->wy; + } + if (*h < bh) + *h = bh; + if (*w < bh) + *w = bh; + if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { + 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)) + click = ClkStatusText; + else + click = ClkWinTitle; + } else if ((c = wintoclient(ev->window))) { + focus(c); + restack(selmon); + XAllowEvents(dpy, ReplayPointer, CurrentTime); + click = ClkClientWin; + } + for (i = 0; i < LENGTH(buttons); i++) + if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button + && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) + buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); +} + +void +checkotherwm(void) +{ + xerrorxlib = XSetErrorHandler(xerrorstart); + /* this causes an error if some other window manager is running */ + XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); + XSync(dpy, False); + XSetErrorHandler(xerror); + XSync(dpy, False); +} + +void +cleanup(void) +{ + Arg a = {.ui = ~0}; + Layout foo = { "", NULL }; + Monitor *m; + size_t i; + + view(&a); + selmon->lt[selmon->sellt] = &foo; + for (m = mons; m; m = m->next) + while (m->stack) + unmanage(m->stack, 0); + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); + for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); + for (i = 0; i < LENGTH(colors); i++) + free(scheme[i]); + 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) +{ + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { + if (cme->data.l[1] == netatom[NetWMFullscreen] + || cme->data.l[2] == netatom[NetWMFullscreen]) + setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ + || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); + } else if (cme->message_type == netatom[NetActiveWindow]) { + if (c != selmon->sel && !c->isurgent) + seturgent(c, 1); + } +} + +void +configure(Client *c) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = c->win; + ce.window = c->win; + ce.x = c->x; + ce.y = c->y; + ce.width = c->w; + ce.height = c->h; + ce.border_width = c->bw; + ce.above = None; + ce.override_redirect = False; + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); +} + +void +configurenotify(XEvent *e) +{ + Monitor *m; + Client *c; + XConfigureEvent *ev = &e->xconfigure; + int dirty; + + /* TODO: updategeom handling sucks, needs to be simplified */ + if (ev->window == root) { + dirty = (sw != ev->width || sh != ev->height); + sw = ev->width; + sh = ev->height; + if (updategeom() || dirty) { + drw_resize(drw, sw, bh); + updatebars(); + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); + XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); + } + focus(NULL); + arrange(NULL); + } + } +} + +void +configurerequest(XEvent *e) +{ + Client *c; + Monitor *m; + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + + if ((c = wintoclient(ev->window))) { + if (ev->value_mask & CWBorderWidth) + c->bw = ev->border_width; + else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { + m = c->mon; + if (ev->value_mask & CWX) { + c->oldx = c->x; + c->x = m->mx + ev->x; + } + if (ev->value_mask & CWY) { + c->oldy = c->y; + c->y = m->my + ev->y; + } + if (ev->value_mask & CWWidth) { + c->oldw = c->w; + c->w = ev->width; + } + if (ev->value_mask & CWHeight) { + c->oldh = c->h; + c->h = ev->height; + } + if ((c->x + c->w) > m->mx + m->mw && c->isfloating) + c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */ + if ((c->y + c->h) > m->my + m->mh && c->isfloating) + c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ + if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) + configure(c); + if (ISVISIBLE(c)) + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + } else + configure(c); + } else { + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + } + XSync(dpy, False); +} + +Monitor * +createmon(void) +{ + Monitor *m; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; + m->mfact = mfact; + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; + m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + return m; +} + +void +destroynotify(XEvent *e) +{ + Client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); +} + +void +detach(Client *c) +{ + Client **tc; + + for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); + *tc = c->next; +} + +void +detachstack(Client *c) +{ + Client **tc, *t; + + for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); + *tc = c->snext; + + if (c == c->mon->sel) { + for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); + c->mon->sel = t; + } +} + +Monitor * +dirtomon(int dir) +{ + Monitor *m = NULL; + + if (dir > 0) { + if (!(m = selmon->next)) + m = mons; + } else if (selmon == mons) + for (m = mons; m->next; m = m->next); + else + for (m = mons; m->next != selmon; m = m->next); + return m; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + unsigned int i, occ = 0, urg = 0; + Client *c; + + if (!m->showbar) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drw_setscheme(drw, scheme[SchemeNorm]); + tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ + drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); + } + + for (c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(tags[i]); + drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drw_rect(drw, x + boxs, boxs, boxw, boxw, + m == selmon && selmon->sel && selmon->sel->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m->ltsymbol); + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + + if ((w = m->ww - tw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); + if (m->sel->isfloating) + drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } + drw_map(drw, m->barwin, 0, 0, m->ww, bh); +} + +void +drawbars(void) +{ + Monitor *m; + + for (m = mons; m; m = m->next) + drawbar(m); +} + +void +enternotify(XEvent *e) +{ + Client *c; + Monitor *m; + XCrossingEvent *ev = &e->xcrossing; + + if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) + return; + c = wintoclient(ev->window); + m = c ? c->mon : wintomon(ev->window); + if (m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + } else if (!c || c == selmon->sel) + return; + focus(c); +} + +void +expose(XEvent *e) +{ + Monitor *m; + XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && (m = wintomon(ev->window))) + drawbar(m); +} + +void +focus(Client *c) +{ + if (!c || !ISVISIBLE(c)) + for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); + if (selmon->sel && selmon->sel != c) + unfocus(selmon->sel, 0); + if (c) { + if (c->mon != selmon) + selmon = c->mon; + if (c->isurgent) + seturgent(c, 0); + detachstack(c); + attachstack(c); + grabbuttons(c, 1); + XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); + setfocus(c); + } else { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } + selmon->sel = c; + drawbars(); +} + +/* there are some broken focus acquiring clients needing extra handling */ +void +focusin(XEvent *e) +{ + XFocusChangeEvent *ev = &e->xfocus; + + if (selmon->sel && ev->window != selmon->sel->win) + setfocus(selmon->sel); +} + +void +focusmon(const Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + if ((m = dirtomon(arg->i)) == selmon) + return; + unfocus(selmon->sel, 0); + selmon = m; + focus(NULL); +} + +void +focusstack(const Arg *arg) +{ + Client *c = NULL, *i; + + if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen)) + return; + if (arg->i > 0) { + for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + if (!c) + for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + } else { + for (i = selmon->clients; i != selmon->sel; i = i->next) + if (ISVISIBLE(i)) + c = i; + if (!c) + for (; i; i = i->next) + if (ISVISIBLE(i)) + c = i; + } + if (c) { + focus(c); + restack(selmon); + } +} + +Atom +getatomprop(Client *c, Atom prop) +{ + int di; + unsigned long dl; + unsigned char *p = NULL; + Atom da, atom = None; + + if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; + XFree(p); + } + return atom; +} + +int +getrootptr(int *x, int *y) +{ + int di; + unsigned int dui; + Window dummy; + + return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); +} + +long +getstate(Window w) +{ + int format; + long result = -1; + unsigned char *p = NULL; + unsigned long n, extra; + Atom real; + + if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return -1; + if (n != 0) + result = *p; + XFree(p); + return result; +} + +int +gettextprop(Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return 0; + text[0] = '\0'; + if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) + return 0; + if (name.encoding == XA_STRING) { + strncpy(text, (char *)name.value, size - 1); + } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + text[size - 1] = '\0'; + XFree(name.value); + return 1; +} + +void +grabbuttons(Client *c, int focused) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + if (!focused) + XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, + BUTTONMASK, GrabModeSync, GrabModeSync, None, None); + for (i = 0; i < LENGTH(buttons); i++) + if (buttons[i].click == ClkClientWin) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabButton(dpy, buttons[i].button, + buttons[i].mask | modifiers[j], + c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + } +} + +void +grabkeys(void) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode code; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < LENGTH(keys); i++) + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, code, keys[i].mod | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); + } +} + +void +incnmaster(const Arg *arg) +{ + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +#ifdef XINERAMA +static int +isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) +{ + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return 0; + return 1; +} +#endif /* XINERAMA */ + +void +keypress(XEvent *e) +{ + unsigned int i; + KeySym keysym; + XKeyEvent *ev; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); + for (i = 0; i < LENGTH(keys); i++) + if (keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); +} + +void +killclient(const Arg *arg) +{ + if (!selmon->sel) + return; + if (!sendevent(selmon->sel, wmatom[WMDelete])) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); + XKillClient(dpy, selmon->sel->win); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } +} + +void +manage(Window w, XWindowAttributes *wa) +{ + Client *c, *t = NULL; + Window trans = None; + XWindowChanges wc; + + c = ecalloc(1, sizeof(Client)); + c->win = w; + /* geometry */ + c->x = c->oldx = wa->x; + c->y = c->oldy = wa->y; + c->w = c->oldw = wa->width; + c->h = c->oldh = wa->height; + c->oldbw = wa->border_width; + + updatetitle(c); + if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { + c->mon = t->mon; + c->tags = t->tags; + } else { + c->mon = selmon; + applyrules(c); + } + + if (c->x + WIDTH(c) > c->mon->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); + { + int format; + unsigned long *data, n, extra; + Monitor *m; + Atom atom; + if (XGetWindowProperty(dpy, c->win, netatom[NetClientInfo], 0L, 2L, False, XA_CARDINAL, + &atom, &format, &n, &extra, (unsigned char **)&data) == Success && n == 2) { + c->tags = *data; + for (m = mons; m; m = m->next) { + if (m->num == *(data+1)) { + c->mon = m; + break; + } + } + } + if (n > 0) + XFree(data); + } + setclienttagprop(c); + + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + if (!c->isfloating) + c->isfloating = c->oldstate = trans != None || c->isfixed; + if (c->isfloating) + XRaiseWindow(dpy, c->win); + attach(c); + attachstack(c); + XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); + XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ + setclientstate(c, NormalState); + if (c->mon == selmon) + unfocus(selmon->sel, 0); + c->mon->sel = c; + arrange(c->mon); + XMapWindow(dpy, c->win); + focus(NULL); +} + +void +mappingnotify(XEvent *e) +{ + XMappingEvent *ev = &e->xmapping; + + XRefreshKeyboardMapping(ev); + if (ev->request == MappingKeyboard) + grabkeys(); +} + +void +maprequest(XEvent *e) +{ + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + + if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redirect) + return; + if (!wintoclient(ev->window)) + manage(ev->window, &wa); +} + +void +monocle(Monitor *m) +{ + unsigned int n = 0; + Client *c; + + for (c = m->clients; c; c = c->next) + if (ISVISIBLE(c)) + n++; + if (n > 0) /* override layout symbol */ + snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); + for (c = nexttiled(m->clients); c; c = nexttiled(c->next)) + resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0); +} + +void +motionnotify(XEvent *e) +{ + static Monitor *mon = NULL; + Monitor *m; + XMotionEvent *ev = &e->xmotion; + + if (ev->window != root) + return; + if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + mon = m; +} + +void +movemouse(const Arg *arg) +{ + int x, y, ocx, ocy, nx, ny; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess) + return; + if (!getrootptr(&x, &y)) + return; + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nx = ocx + (ev.xmotion.x - x); + ny = ocy + (ev.xmotion.y - y); + if (abs(selmon->wx - nx) < snap) + nx = selmon->wx; + else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap) + nx = selmon->wx + selmon->ww - WIDTH(c); + if (abs(selmon->wy - ny) < snap) + ny = selmon->wy; + else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap) + ny = selmon->wy + selmon->wh - HEIGHT(c); + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) + togglefloating(NULL); + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, nx, ny, c->w, c->h, 1); + break; + } + } while (ev.type != ButtonRelease); + XUngrabPointer(dpy, CurrentTime); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +Client * +nexttiled(Client *c) +{ + for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); + return c; +} + +void +pop(Client *c) +{ + detach(c); + attach(c); + focus(c); + arrange(c->mon); +} + +void +propertynotify(XEvent *e) +{ + Client *c; + Window trans; + XPropertyEvent *ev = &e->xproperty; + + if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ + else if ((c = wintoclient(ev->window))) { + switch(ev->atom) { + default: break; + case XA_WM_TRANSIENT_FOR: + if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && + (c->isfloating = (wintoclient(trans)) != NULL)) + arrange(c->mon); + break; + case XA_WM_NORMAL_HINTS: + 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); + } + 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 +resize(Client *c, int x, int y, int w, int h, int interact) +{ + if (applysizehints(c, &x, &y, &w, &h, interact)) + resizeclient(c, x, y, w, h); +} + +void +resizeclient(Client *c, int x, int y, int w, int h) +{ + XWindowChanges wc; + + c->oldx = c->x; c->x = wc.x = x; + c->oldy = c->y; c->y = wc.y = y; + c->oldw = c->w; c->w = wc.width = w; + c->oldh = c->h; c->h = wc.height = h; + wc.border_width = c->bw; + XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); + configure(c); + XSync(dpy, False); +} + +void +resizemouse(const Arg *arg) +{ + int ocx, ocy, nw, nh; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess) + return; + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1); + nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1); + if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww + && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh) + { + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nw - c->w) > snap || abs(nh - c->h) > snap)) + togglefloating(NULL); + } + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, c->x, c->y, nw, nh, 1); + break; + } + } while (ev.type != ButtonRelease); + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + XUngrabPointer(dpy, CurrentTime); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +void +restack(Monitor *m) +{ + Client *c; + XEvent ev; + XWindowChanges wc; + + drawbar(m); + if (!m->sel) + return; + if (m->sel->isfloating || !m->lt[m->sellt]->arrange) + XRaiseWindow(dpy, m->sel->win); + if (m->lt[m->sellt]->arrange) { + wc.stack_mode = Below; + wc.sibling = m->barwin; + for (c = m->stack; c; c = c->snext) + if (!c->isfloating && ISVISIBLE(c)) { + XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); + wc.sibling = c->win; + } + } + XSync(dpy, False); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); +} + +void +run(void) +{ + XEvent ev; + /* main event loop */ + XSync(dpy, False); + while (running && !XNextEvent(dpy, &ev)) + if (handler[ev.type]) + handler[ev.type](&ev); /* call handler */ +} + +void +scan(void) +{ + unsigned int i, num; + Window d1, d2, *wins = NULL; + XWindowAttributes wa; + + if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { + for (i = 0; i < num; i++) { + if (!XGetWindowAttributes(dpy, wins[i], &wa) + || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1)) + continue; + if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) + manage(wins[i], &wa); + } + for (i = 0; i < num; i++) { /* now the transients */ + if (!XGetWindowAttributes(dpy, wins[i], &wa)) + continue; + if (XGetTransientForHint(dpy, wins[i], &d1) + && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) + manage(wins[i], &wa); + } + if (wins) + XFree(wins); + } +} + +void +sendmon(Client *c, Monitor *m) +{ + if (c->mon == m) + return; + unfocus(c, 1); + detach(c); + detachstack(c); + c->mon = m; + c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ + attach(c); + attachstack(c); + setclienttagprop(c); + focus(NULL); + arrange(NULL); +} + +void +setclientstate(Client *c, long state) +{ + long data[] = { state, None }; + + XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32, + PropModeReplace, (unsigned char *)data, 2); +} + +int +sendevent(Client *c, Atom proto) +{ + int n; + Atom *protocols; + int exists = 0; + XEvent ev; + + if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { + while (!exists && n--) + exists = protocols[n] == proto; + XFree(protocols); + } + if (exists) { + ev.type = ClientMessage; + ev.xclient.window = c->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = proto; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, c->win, False, NoEventMask, &ev); + } + return exists; +} + +void +setfocus(Client *c) +{ + if (!c->neverfocus) { + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XChangeProperty(dpy, root, netatom[NetActiveWindow], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(c->win), 1); + } + sendevent(c, wmatom[WMTakeFocus]); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + if (fullscreen && !c->isfullscreen) { + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); + c->isfullscreen = 1; + c->oldstate = c->isfloating; + c->oldbw = c->bw; + c->bw = 0; + c->isfloating = 1; + resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh); + XRaiseWindow(dpy, c->win); + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)0, 0); + c->isfullscreen = 0; + c->isfloating = c->oldstate; + c->bw = c->oldbw; + c->x = c->oldx; + c->y = c->oldy; + c->w = c->oldw; + c->h = c->oldh; + resizeclient(c, c->x, c->y, c->w, c->h); + arrange(c->mon); + } +} + +void +setgaps(const Arg *arg) +{ + if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) + selmon->gappx = 0; + else + selmon->gappx += arg->i; + arrange(selmon); +} + +void +setlayout(const Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); + if (selmon->sel) + arrange(selmon); + else + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + if (f < 0.05 || f > 0.95) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + + /* clean up any zombies immediately */ + sigchld(0); + + 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 + 2; + updategeom(); + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + netatom[NetClientInfo] = XInternAtom(dpy, "_NET_CLIENT_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 *)); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); + /* init bars */ + updatebars(); + updatestatus(); + /* supporting window for NetWMCheck */ + wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8, + PropModeReplace, (unsigned char *) "dwm", 3); + XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + /* EWMH support per view */ + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); + XDeleteProperty(dpy, root, netatom[NetClientInfo]); + /* select events */ + wa.cursor = cursor[CurNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + |ButtonPressMask|PointerMotionMask|EnterWindowMask + |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; + XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(NULL); +} + +void +seturgent(Client *c, int urg) +{ + XWMHints *wmh; + + c->isurgent = urg; + if (!(wmh = XGetWMHints(dpy, c->win))) + return; + wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint); + XSetWMHints(dpy, c->win, wmh); + XFree(wmh); +} + +void +showhide(Client *c) +{ + if (!c) + return; + if (ISVISIBLE(c)) { + /* show clients top down */ + XMoveWindow(dpy, c->win, c->x, c->y); + if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen) + resize(c, c->x, c->y, c->w, c->h, 0); + showhide(c->snext); + } else { + /* hide clients bottom up */ + showhide(c->snext); + XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y); + } +} + +void +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("can't install SIGCHLD handler:"); + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +void +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 +setclienttagprop(Client *c) +{ + long data[] = { (long) c->tags, (long) c->mon->num }; + XChangeProperty(dpy, c->win, netatom[NetClientInfo], XA_CARDINAL, 32, + PropModeReplace, (unsigned char *) data, 2); +} + +void +tag(const Arg *arg) +{ + Client *c; + if (selmon->sel && arg->ui & TAGMASK) { + c = selmon->sel; + selmon->sel->tags = arg->ui & TAGMASK; + setclienttagprop(c); + focus(NULL); + arrange(selmon); + } +} + +void +tagmon(const Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendmon(selmon->sel, dirtomon(arg->i)); +} + +void +tile(Monitor *m) +{ + unsigned int i, n, h, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else + mw = m->ww - m->gappx; + for (i = 0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + if (i < m->nmaster) { + h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; + resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); + if (my + HEIGHT(c) + m->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; + } +} + +void +togglebar(const Arg *arg) +{ + selmon->showbar = !selmon->showbar; + updatebarpos(selmon); + XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); + arrange(selmon); +} + +void +togglefloating(const Arg *arg) +{ + if (!selmon->sel) + return; + if (selmon->sel->isfullscreen) /* no support for fullscreen windows */ + return; + selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; + if (selmon->sel->isfloating) + resize(selmon->sel, selmon->sel->x, selmon->sel->y, + selmon->sel->w, selmon->sel->h, 0); + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + unsigned int newtags; + + if (!selmon->sel) + return; + newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + if (newtags) { + selmon->sel->tags = newtags; + setclienttagprop(selmon->sel); + focus(NULL); + arrange(selmon); + } +} + +void +toggleview(const Arg *arg) +{ + unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + focus(NULL); + arrange(selmon); + } +} + +void +unfocus(Client *c, int setfocus) +{ + if (!c) + return; + grabbuttons(c, 0); + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + if (setfocus) { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } +} + +void +unmanage(Client *c, int destroyed) +{ + Monitor *m = c->mon; + XWindowChanges wc; + + detach(c); + detachstack(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + 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); + } +} + +void +updatebars(void) +{ + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, + .background_pixmap = ParentRelative, + .event_mask = ButtonPressMask|ExposureMask + }; + XClassHint ch = {"dwm", "dwm"}; + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; + m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +} + +void +updatebarpos(Monitor *m) +{ + m->wy = m->my; + m->wh = m->mh; + if (m->showbar) { + m->wh -= bh; + m->by = m->topbar ? m->wy : m->wy + m->wh; + m->wy = m->topbar ? m->wy + bh : m->wy; + } else + m->by = -bh; +} + +void +updateclientlist() +{ + Client *c; + Monitor *m; + + XDeleteProperty(dpy, root, netatom[NetClientList]); + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + XChangeProperty(dpy, root, netatom[NetClientList], + XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); +} + +int +updategeom(void) +{ + int dirty = 0; + +#ifdef XINERAMA + if (XineramaIsActive(dpy)) { + int i, j, n, nn; + Client *c; + Monitor *m; + XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn); + XineramaScreenInfo *unique = NULL; + + for (n = 0, m = mons; m; m = m->next, n++); + /* only consider unique geometries as separate screens */ + unique = ecalloc(nn, sizeof(XineramaScreenInfo)); + for (i = 0, j = 0; i < nn; i++) + if (isuniquegeom(unique, j, &info[i])) + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); + XFree(info); + nn = j; + + /* 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); +} + +void +updatetitle(Client *c) +{ + if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) + gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); + if (c->name[0] == '\0') /* hack to mark broken clients */ + strcpy(c->name, broken); +} + +void +updatewindowtype(Client *c) +{ + Atom state = getatomprop(c, netatom[NetWMState]); + Atom wtype = getatomprop(c, netatom[NetWMWindowType]); + + if (state == netatom[NetWMFullscreen]) + setfullscreen(c, 1); + if (wtype == netatom[NetWMWindowTypeDialog]) + c->isfloating = 1; +} + +void +updatewmhints(Client *c) +{ + XWMHints *wmh; + + if ((wmh = XGetWMHints(dpy, c->win))) { + if (c == selmon->sel && wmh->flags & XUrgencyHint) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, c->win, wmh); + } else + c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} + +void +view(const Arg *arg) +{ + if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); +} + +Client * +wintoclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + if (c->win == w) + return c; + return NULL; +} + +Monitor * +wintomon(Window w) +{ + int x, y; + Client *c; + Monitor *m; + + if (w == root && getrootptr(&x, &y)) + return recttomon(x, y, 1, 1); + for (m = mons; m; m = m->next) + if (w == m->barwin) + return m; + if ((c = wintoclient(w))) + return c->mon; + return selmon; +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) + return 0; + fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +int +xerrordummy(Display *dpy, XErrorEvent *ee) +{ + return 0; +} + +/* Startup Error handler to check if another window manager + * is already running. */ +int +xerrorstart(Display *dpy, XErrorEvent *ee) +{ + die("dwm: another window manager is already running"); + return -1; +} + +void +zoom(const Arg *arg) +{ + Client *c = selmon->sel; + + if (!selmon->lt[selmon->sellt]->arrange || !c || c->isfloating) + return; + if (c == nexttiled(selmon->clients) && !(c = nexttiled(c->next))) + return; + pop(c); +} + +int +main(int argc, char *argv[]) +{ + if (argc == 2 && !strcmp("-v", argv[1])) + die("dwm-"VERSION); + else if (argc != 1) + die("usage: dwm [-v]"); + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("dwm: cannot open display"); + checkotherwm(); + setup(); +#ifdef __OpenBSD__ + if (pledge("stdio rpath proc exec", NULL) == -1) + die("pledge"); +#endif /* __OpenBSD__ */ + scan(); + run(); + if(restart) execvp(argv[0], argv); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/dwm/dwm.o b/dwm/dwm.o new file mode 100644 index 0000000000000000000000000000000000000000..d4823889ab4de5746e2a0d04e5941abcbbc2962d GIT binary patch literal 61120 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 literal 0 HcmV?d00001 diff --git a/dwm/dwm.png b/dwm/dwm.png new file mode 100644 index 0000000000000000000000000000000000000000..b1f9ba7e5f4cc7350ee2392ebcea5fcbe00fb49b GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^2Y@($g9*gC@m3f}u_bxCyDx`7I;J! zGca%iWx0hJ8D`Cq01C2~c>21sUt<^MF=V?Ztt9{yk}YwKC~?lu%}vcKVQ?-=O)N=G zQ7F$W$xsN%NL6t6^bL5QqM8R(c+=CxF{I+w+q;fj4F)_6j>`Z3pZ>_($QEQ&92OXP z%lpEKGwG8$G-U1H{@Y%;mx-mNK|p|siBVAj$Z~Mt-~h6K0!}~{PyozQ07(f5fTdVi zm=-zT`NweeJ#%S&{fequZGmkDDC*%x$$Sa*fAP=$`nJkhx1Y~k<8b2;Hq)FOdV=P$ q&oWzoxz_&nv&n0)xBzV8k*jsxheTIy&cCY600f?{elF{r5}E*x)opSB literal 0 HcmV?d00001 diff --git a/dwm/transient.c b/dwm/transient.c new file mode 100644 index 0000000..040adb5 --- /dev/null +++ b/dwm/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/dwm/util.c b/dwm/util.c new file mode 100644 index 0000000..96b82c9 --- /dev/null +++ b/dwm/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/dwm/util.h b/dwm/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/dwm/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/dwm/util.o b/dwm/util.o new file mode 100644 index 0000000000000000000000000000000000000000..6ab501f771232ee94bd1d5e3f8348ea759683421 GIT binary patch literal 2216 zcmbtVJ!n%=6h5!5Hr6zWZHG!7B0MF5`e+J5NmZha_9;PXRn#t;CNDOaKVNPnRl$mN z2qnHbf`g!=(4d2>qmy+M>QDy{=7Uon>yE} zX&_C5Q?NUeEI?$>uAks;0s_zvm(9ku`S`~-q|64>C(OnbJz+Ldx|wPu^_diU*Oyx? z9*idSTft~buLPsd^d*!BqtEqKjxY2zj<5AM9N+0Jj_>u)h*@)`=STz{6AMOSEpz20 zPH^_sY%c3tA~0+=ujnDOnbgNVCH0UGX~dy15e|Px2bLzmTscl28#Nmn)Ub~S%oX&O z9(K3mwZZi;#yJ~nbJ*-#?~~Lz?G0xyIf+kOHNV;99dTmrV8YuBI5K_l zYBU@hH^vR>1$uCVanN>R2)aOP5hwRt6Z3t@kqtQj+WlG3>LG2!-|JgN@i6j-MC>== z=|Jd7*M-2Ah)sytK@qza@Cn8nX#F1e+4hQd?{v2tb*d?sLWKGe2NXT;4Y>n2LPDbg z{SGhwa0fopfj2tvblP6^@qBonvOm60$W3<8LyhGNMh1-3)EH#*7F1c*vTeARtJwJx z%i-KESS83+YAgd443%xL>Ujp1b0M%US2x@N^gU;*k7X!MpVz6n(Fv z|Dy18MzYR^!qeT7{HDUI`dx-lsLyU3QtuZ${Vp1k52H^;dK%JB z2uJere88wK7Fl{8k=YI|NYAzkX@gmH21Yi`(qPP2t6mb}a5HD8i64*ouK$p!3#N2+-BVi3EoYQB2P8{H9v;ARC64MYI1$q zuZ+p{B_=RHKL3bH5#pQ>3@RMCGEd@pg;S+PDNsod?G5DUovC+~QYs?f-Ix3K+(j)( z-T7-Ge};-F=FTr*%HO(^%Y6F2l6kF7JpEsgO$eknO(os=RMUO-NI^u9 aXK13BD7VAV_uni3rRe{`j?!`5`F{Z;iVAT6 literal 0 HcmV?d00001 diff --git a/hypr/hyprland.conf b/hypr/hyprland.conf new file mode 100644 index 0000000..30eff5c --- /dev/null +++ b/hypr/hyprland.conf @@ -0,0 +1,164 @@ + +######################################################################################## +AUTOGENERATED HYPR CONFIG. +PLEASE USE THE CONFIG PROVIDED IN THE GIT REPO /examples/hypr.conf AND EDIT IT, +OR EDIT THIS ONE ACCORDING TO THE WIKI INSTRUCTIONS. +######################################################################################## + +# +# Please note not all available settings / options are set here. +# For a full list, see the wiki +# + +#autogenerated = 1 # remove this line to remove the warning + +# See https://wiki.hyprland.org/Configuring/Monitors/ +monitor=eDP-1,1920x1080@144,1920x0,1 + + +# See https://wiki.hyprland.org/Configuring/Keywords/ for more + +# Execute your favorite apps at launch +exec-once = waybar & hyprpaper + +# Source a file (multi-file configs) +#source = ~/.config/hypr/superconfig.conf + +# For all categories, see https://wiki.hyprland.org/Configuring/Variables/ +input { + kb_layout = es + kb_variant = + kb_model = + kb_options = + kb_rules = + + follow_mouse = 1 + + touchpad { + natural_scroll = no + } + + sensitivity = 0 # -1.0 - 1.0, 0 means no modification. +} + +general { + # See https://wiki.hyprland.org/Configuring/Variables/ for more + + gaps_in = 5 + gaps_out = 20 + border_size = 2 + col.active_border = rgb(ff69b4) + col.inactive_border = rgb(f3cfc6) + + layout = dwindle +} + +decoration { + # See https://wiki.hyprland.org/Configuring/Variables/ for more + + rounding = 10 + blur = yes + blur_size = 3 + blur_passes = 1 + blur_new_optimizations = on + + drop_shadow = yes + shadow_range = 4 + shadow_render_power = 3 + col.shadow = rgba(1a1a1aee) +} + +animations { + enabled = yes + + # Some default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more + + bezier = myBezier, 0.05, 0.9, 0.1, 1.05 + + animation = windows, 1, 7, myBezier + animation = windowsOut, 1, 7, default, popin 80% + animation = border, 1, 10, default + animation = fade, 1, 7, default + animation = workspaces, 1, 6, default +} + +dwindle { + # See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more + pseudotile = yes # master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below + preserve_split = yes # you probably want this +} + +master { + # See https://wiki.hyprland.org/Configuring/Master-Layout/ for more + new_is_master = true +} + +gestures { + # See https://wiki.hyprland.org/Configuring/Variables/ for more + workspace_swipe = off +} + +# Example per-device config +# See https://wiki.hyprland.org/Configuring/Keywords/#executing for more +device:epic mouse V1 { + sensitivity = -0.5 +} + +# Example windowrule v1 +# windowrule = float, ^(kitty)$ +# Example windowrule v2 +# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$ +# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more + + +# See https://wiki.hyprland.org/Configuring/Keywords/ for more +$mainMod = SUPER + +# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more +bind = $mainMod, D, exec, wofi --show run +bind = $mainMod, Q, exec, kitty +bind = $mainMod, C, killactive, +bind = $mainMod, M, exit, +bind = $mainMod, E, exec, dolphin +bind = $mainMod, V, togglefloating, +bind = $mainMod, R, exec, wofi --show drun +bind = $mainMod, P, pseudo, # dwindle +bind = $mainMod, J, togglesplit, # dwindle + +# Move focus with mainMod + arrow keys +bind = $mainMod, left, movefocus, l +bind = $mainMod, right, movefocus, r +bind = $mainMod, up, movefocus, u +bind = $mainMod, down, movefocus, d + +# Switch workspaces with mainMod + [0-9] +bind = $mainMod, 1, workspace, 1 +bind = $mainMod, 2, workspace, 2 +bind = $mainMod, 3, workspace, 3 +bind = $mainMod, 4, workspace, 4 +bind = $mainMod, 5, workspace, 5 +bind = $mainMod, 6, workspace, 6 +bind = $mainMod, 7, workspace, 7 +bind = $mainMod, 8, workspace, 8 +bind = $mainMod, 9, workspace, 9 +bind = $mainMod, 0, workspace, 10 + +# Move active window to a workspace with mainMod + SHIFT + [0-9] +bind = $mainMod SHIFT, 1, movetoworkspace, 1 +bind = $mainMod SHIFT, 2, movetoworkspace, 2 +bind = $mainMod SHIFT, 3, movetoworkspace, 3 +bind = $mainMod SHIFT, 4, movetoworkspace, 4 +bind = $mainMod SHIFT, 5, movetoworkspace, 5 +bind = $mainMod SHIFT, 6, movetoworkspace, 6 +bind = $mainMod SHIFT, 7, movetoworkspace, 7 +bind = $mainMod SHIFT, 8, movetoworkspace, 8 +bind = $mainMod SHIFT, 9, movetoworkspace, 9 +bind = $mainMod SHIFT, 0, movetoworkspace, 10 + +# Scroll through existing workspaces with mainMod + scroll +bind = $mainMod, mouse_down, workspace, e+1 +bind = $mainMod, mouse_up, workspace, e-1 + +# Move/resize windows with mainMod + LMB/RMB and dragging +bindm = $mainMod, mouse:272, movewindow +bindm = $mainMod, mouse:273, resizewindow diff --git a/hypr/hyprpaper.conf b/hypr/hyprpaper.conf new file mode 100644 index 0000000..fc062bf --- /dev/null +++ b/hypr/hyprpaper.conf @@ -0,0 +1,3 @@ +preload = /home/sml/Descargas/1.jpg +wallpaper = eDP-1,/home/sml/Descargas/1.jpg + diff --git a/kitty/kitty.conf b/kitty/kitty.conf new file mode 100644 index 0000000..a2e8c37 --- /dev/null +++ b/kitty/kitty.conf @@ -0,0 +1,244 @@ +# vim:fileencoding=utf-8:ft=conf + +# Font family. You can also specify different fonts for the +# bold/italic/bold-italic variants. By default they are derived automatically, +# by the OSes font system. Setting them manually is useful for font families +# that have many weight variants like Book, Medium, Thick, etc. For example: +# font_family Operator Mono Book +# bold_font Operator Mono Thick +# bold_italic_font Operator Mono Medium +# font_family Input Mono +font_family Fantasque Sans Mono +italic_font auto +bold_font auto +bold_italic_font auto +background_opacity 0.9 + +# Font size (in pts) +font_size 10.0 + +# The foreground color +foreground #cea2bc + +# The background color +background #262626 + +# The foreground for selections +selection_foreground #2f2f2f + +# The background for selections +selection_background #d75f5f + +# The cursor color +cursor #8fee96 + +# The cursor shape can be one of (block, beam, underline) +shell_integration no-cursor +cursor_shape block + +# The interval (in seconds) at which to blink the cursor. Set to zero to +# disable blinking. +cursor_blink_interval 0.5 + +# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to +# zero or a negative number to never stop blinking. +cursor_stop_blinking_after 15.0 + +# Number of lines of history to keep in memory for scrolling back +scrollback_lines 2000 + +# Program with which to view scrollback in a new window. The scrollback buffer is passed as +# STDIN to this program. If you change it, make sure the program you use can +# handle ANSI escape sequences for colors and text formatting. +scrollback_pager less +G -R + +# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel) +wheel_scroll_multiplier 5.0 + +# The interval between successive clicks to detect double/triple clicks (in seconds) +click_interval 0.5 + +# Characters considered part of a word when double clicking. In addition to these characters +# any character that is marked as an alpha-numeric character in the unicode +# database will be matched. +select_by_word_characters :@-./_~?&=%+# + +# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to +# zero or a negative number to disable mouse cursor hiding. +mouse_hide_wait 0.0 + +# The enabled window layouts. A comma separated list of layout names. The special value * means +# all layouts. The first listed layout will be used as the startup layout. +# For a list of available layouts, see the file layouts.py +enabled_layouts * + +# If enabled, the window size will be remembered so that new instances of kitty will have the same +# size as the previous instance. If disabled, the window will initially have size configured +# by initial_window_width/height, in pixels. +remember_window_size no +initial_window_width 640 +initial_window_height 400 + +# Delay (in milliseconds) between screen updates. Decreasing it, increases fps +# at the cost of more CPU usage. The default value yields ~100fps which is more +# that sufficient for most uses. +# repaint_delay 10 +repaint_delay 10 + +# Delay (in milliseconds) before input from the program running in the terminal +# is processed. Note that decreasing it will increase responsiveness, but also +# increase CPU usage and might cause flicker in full screen programs that +# redraw the entire screen on each loop, because kitty is so fast that partial +# screen updates will be drawn. +input_delay 3 + +# Visual bell duration. Flash the screen when a bell occurs for the specified number of +# seconds. Set to zero to disable. +visual_bell_duration 0.0 + +# Enable/disable the audio bell. Useful in environments that require silence. +enable_audio_bell yes + +# The modifier keys to press when clicking with the mouse on URLs to open the URL +open_url_modifiers ctrl+shift + +# The program with which to open URLs that are clicked on. The special value "default" means to +# use the operating system's default URL handler. +open_url_with default + +# The value of the TERM environment variable to set +term xterm-kitty + +# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution. +window_border_width 0 + +window_margin_width 15 + +# The color for the border of the active window +active_border_color #ffffff + +# The color for the border of inactive windows +inactive_border_color #cccccc + +# Tab-bar colors +active_tab_foreground #000 +active_tab_background #eee +inactive_tab_foreground #444 +inactive_tab_background #999 + + +# The 16 terminal colors. There are 8 basic colors, each color has a dull and +# bright version. + +# black +color0 #2f2f2f +color8 #656565 + +# red +color1 #d75f5f +color9 #d75f5f + +# green +color2 #d4d232 +color10 #8fee96 + +# yellow +color3 #af865a +color11 #cd950c + +# blue +color4 #22c3a1 +color12 #22c3a1 + +# magenta +color5 #775759 +color13 #775759 + +# cyan +color6 #84edb9 +color14 #84edb9 + +# white +color7 #c0b18b +color15 #d8d8d8 + +# Key mapping +# For a list of key names, see: http://www.glfw.org/docs/latest/group__keys.html +# For a list of modifier names, see: http://www.glfw.org/docs/latest/group__mods.html +# You can use the special action no_op to unmap a keyboard shortcut that is +# assigned in the default configuration. + +# Clipboard +map super+v paste_from_clipboard +map ctrl+shift+s paste_from_selection +map super+c copy_to_clipboard +map shift+insert paste_from_selection + +# Scrolling +map ctrl+shift+up scroll_line_up +map ctrl+shift+down scroll_line_down +map ctrl+shift+k scroll_line_up +map ctrl+shift+j scroll_line_down +map ctrl+shift+page_up scroll_page_up +map ctrl+shift+page_down scroll_page_down +map ctrl+shift+home scroll_home +map ctrl+shift+end scroll_end +map ctrl+shift+h show_scrollback + +# Window management +map super+n new_os_window +map super+w close_window +map ctrl+shift+enter new_window +map ctrl+shift+] next_window +map ctrl+shift+[ previous_window +map ctrl+shift+f move_window_forward +map ctrl+shift+b move_window_backward +map ctrl+shift+` move_window_to_top +map ctrl+shift+1 first_window +map ctrl+shift+2 second_window +map ctrl+shift+3 third_window +map ctrl+shift+4 fourth_window +map ctrl+shift+5 fifth_window +map ctrl+shift+6 sixth_window +map ctrl+shift+7 seventh_window +map ctrl+shift+8 eighth_window +map ctrl+shift+9 ninth_window +map ctrl+shift+0 tenth_window + +# Tab management +map ctrl+shift+right next_tab +map ctrl+shift+left previous_tab +map ctrl+shift+t new_tab +map ctrl+shift+q close_tab +map ctrl+shift+l next_layout +map ctrl+shift+. move_tab_forward +map ctrl+shift+, move_tab_backward + +# Miscellaneous +map ctrl+shift+up increase_font_size +map ctrl+shift+down decrease_font_size +map ctrl+shift+backspace restore_font_size + +# Symbol mapping (special font for specified unicode code points). Map the +# specified unicode codepoints to a particular font. Useful if you need special +# rendering for some symbols, such as for Powerline. Avoids the need for +# patched fonts. Each unicode code point is specified in the form U+. You can specify multiple code points, separated by commas +# and ranges separated by hyphens. symbol_map itself can be specified multiple times. +# Syntax is: +# +# symbol_map codepoints Font Family Name +# +# For example: +# +#symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols +hide_window_decorations titlebar-only +macos_option_as_alt no + +# Change the color of the kitty window's titlebar on macOS. A value of "system" +# means to use the default system color, a value of "background" means to use +# the default background color and finally you can use an arbitrary color, such +# as #12af59 or "red". +macos_titlebar_color background + +allow_remote_control yes diff --git a/st/FAQ b/st/FAQ new file mode 100644 index 0000000..969b195 --- /dev/null +++ b/st/FAQ @@ -0,0 +1,250 @@ +## Why does st not handle utmp entries? + +Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task. + + +## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever! + +It means that st doesn’t have any terminfo entry on your system. Chances are +you did not `make install`. If you just want to test it without installing it, +you can manually run `tic -sx st.info`. + + +## Nothing works, and nothing is said about an unknown terminal! + +* Some programs just assume they’re running in xterm i.e. they don’t rely on + terminfo. What you see is the current state of the “xterm compliance”. +* Some programs don’t complain about the lacking st description and default to + another terminal. In that case see the question about terminfo. + + +## How do I scroll back up? + +* Using a terminal multiplexer. + * `st -e tmux` using C-b [ + * `st -e screen` using C-a ESC +* Using the excellent tool of [scroll](https://git.suckless.org/scroll/). +* Using the scrollback [patch](https://st.suckless.org/patches/scrollback/). + + +## I would like to have utmp and/or scroll functionality by default + +You can add the absolute path of both programs in your config.h file. You only +have to modify the value of utmp and scroll variables. + + +## Why doesn't the Del key work in some programs? + +Taken from the terminfo manpage: + + If the terminal has a keypad that transmits codes when the keys + are pressed, this information can be given. Note that it is not + possible to handle terminals where the keypad only works in + local (this applies, for example, to the unshifted HP 2621 keys). + If the keypad can be set to transmit or not transmit, give these + codes as smkx and rmkx. Otherwise the keypad is assumed to + always transmit. + +In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that +applications which want to test against keypad keys send these +sequences. + +But buggy applications (like bash and irssi, for example) don't do this. A fast +solution for them is to use the following command: + + $ printf '\033[?1h\033=' >/dev/tty + +or + $ tput smkx + +In the case of bash, readline is used. Readline has a different note in its +manpage about this issue: + + enable-keypad (Off) + When set to On, readline will try to enable the + application keypad when it is called. Some systems + need this to enable arrow keys. + +Adding this option to your .inputrc will fix the keypad problem for all +applications using readline. + +If you are using zsh, then read the zsh FAQ +: + + 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. 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..470ac86 --- /dev/null +++ b/st/Makefile @@ -0,0 +1,57 @@ +# st - simple terminal +# See LICENSE file for copyright and license details. +.POSIX: + +include config.mk + +SRC = st.c x.c +OBJ = $(SRC:.c=.o) + +all: options st + +options: + @echo st build options: + @echo "CFLAGS = $(STCFLAGS)" + @echo "LDFLAGS = $(STLDFLAGS)" + @echo "CC = $(CC)" + +config.h: + cp config.def.h config.h + +.c.o: + $(CC) $(STCFLAGS) -c $< + +st.o: config.h st.h win.h +x.o: arg.h config.h st.h win.h + +$(OBJ): config.h config.mk + +st: $(OBJ) + $(CC) -o $@ $(OBJ) $(STLDFLAGS) + +clean: + rm -f st $(OBJ) st-$(VERSION).tar.gz + +dist: clean + mkdir -p st-$(VERSION) + cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ + config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ + st-$(VERSION) + tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz + rm -rf st-$(VERSION) + +install: st + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f st $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/st + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 + tic -sx st.info + @echo Please see the README file regarding the terminfo entry of st. + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/st + rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 + +.PHONY: all options clean dist install uninstall diff --git a/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..10b131d --- /dev/null +++ b/st/config.def.h @@ -0,0 +1,494 @@ +/* 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=14:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + [0] = "#000000", /* black */ + [1] = "#ff5555", /* red */ + [2] = "#50fa7b", /* green */ + [3] = "#f1fa8c", /* yellow */ + [4] = "#bd93f9", /* blue */ + [5] = "#ff79c6", /* magenta */ + [6] = "#8be9fd", /* cyan */ + [7] = "#bbbbbb", /* white */ + + /* 8 bright colors */ + [8] = "#44475a", /* black */ + [9] = "#ff5555", /* red */ + [10] = "#50fa7b", /* green */ + [11] = "#f1fa8c", /* yellow */ + [12] = "#bd93f9", /* blue */ + [13] = "#ff79c6", /* magenta */ + [14] = "#8be9fd", /* cyan */ + [15] = "#ffffff", /* white */ + + /* special colors */ + [256] = "#1d1f21", /* background */ + [257] = "#d8dee9", /* foreground */ + + /* 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 + */ +unsigned int defaultfg = 257; +unsigned int defaultbg = 256; +unsigned int defaultcs = 257; +static unsigned int defaultrcs = 257; + +/* + * Colors used, when the specific fg == defaultfg. So in reverse mode this + * will reverse too. Another logic would only make the simple feature too + * complex. + */ +unsigned int defaultitalic = 7; +unsigned int defaultunderline = 7; + +/* + * 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 */ + { 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"} }, + { 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.def.h.orig b/st/config.def.h.orig new file mode 100644 index 0000000..503c08a --- /dev/null +++ b/st/config.def.h.orig @@ -0,0 +1,484 @@ +/* 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=14:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "black", /* default background colour */ +}; + + + + + +/* + * 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 */ + { 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"} }, + { 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..10b131d --- /dev/null +++ b/st/config.h @@ -0,0 +1,494 @@ +/* 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=14:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + [0] = "#000000", /* black */ + [1] = "#ff5555", /* red */ + [2] = "#50fa7b", /* green */ + [3] = "#f1fa8c", /* yellow */ + [4] = "#bd93f9", /* blue */ + [5] = "#ff79c6", /* magenta */ + [6] = "#8be9fd", /* cyan */ + [7] = "#bbbbbb", /* white */ + + /* 8 bright colors */ + [8] = "#44475a", /* black */ + [9] = "#ff5555", /* red */ + [10] = "#50fa7b", /* green */ + [11] = "#f1fa8c", /* yellow */ + [12] = "#bd93f9", /* blue */ + [13] = "#ff79c6", /* magenta */ + [14] = "#8be9fd", /* cyan */ + [15] = "#ffffff", /* white */ + + /* special colors */ + [256] = "#1d1f21", /* background */ + [257] = "#d8dee9", /* foreground */ + + /* 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 + */ +unsigned int defaultfg = 257; +unsigned int defaultbg = 256; +unsigned int defaultcs = 257; +static unsigned int defaultrcs = 257; + +/* + * Colors used, when the specific fg == defaultfg. So in reverse mode this + * will reverse too. Another logic would only make the simple feature too + * complex. + */ +unsigned int defaultitalic = 7; +unsigned int defaultunderline = 7; + +/* + * 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 */ + { 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"} }, + { 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..7ec090b --- /dev/null +++ b/st/config.mk @@ -0,0 +1,36 @@ +# st version +VERSION = 0.8.5 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# includes and libs +INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +# flags +STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +STLDFLAGS = $(LIBS) $(LDFLAGS) + +# OpenBSD: +#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE +#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ +# `$(PKG_CONFIG) --libs fontconfig` \ +# `$(PKG_CONFIG) --libs freetype2` +#MANPREFIX = ${PREFIX}/man + +# compiler and linker +# CC = c99 diff --git a/st/st b/st/st new file mode 100755 index 0000000000000000000000000000000000000000..d50006434a6706bf6ecb86a66657c832b50f0b89 GIT binary patch literal 111136 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 literal 0 HcmV?d00001 diff --git a/st/st-alpha-20220206-0.8.5.diff b/st/st-alpha-20220206-0.8.5.diff new file mode 100644 index 0000000..ab029f6 --- /dev/null +++ b/st/st-alpha-20220206-0.8.5.diff @@ -0,0 +1,146 @@ +diff --git a/config.def.h b/config.def.h +index 91ab8ca..6af616e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -93,6 +93,9 @@ char *termname = "st-256color"; + */ + unsigned int tabspaces = 8; + ++/* bg opacity */ ++float alpha = 0.8; ++ + /* Terminal colors (16 first used in escape sequence) */ + static const char *colorname[] = { + /* 8 normal colors */ +diff --git a/config.mk b/config.mk +index 4c4c5d5..0114bad 100644 +--- a/config.mk ++++ b/config.mk +@@ -16,7 +16,7 @@ PKG_CONFIG = pkg-config + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +-LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ ++LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +diff --git a/st.h b/st.h +index 519b9bd..8bb533d 100644 +--- a/st.h ++++ b/st.h +@@ -126,3 +126,4 @@ extern unsigned int tabspaces; + extern unsigned int defaultfg; + extern unsigned int defaultbg; + extern unsigned int defaultcs; ++extern float alpha; +diff --git a/x.c b/x.c +index 8a16faa..ddf4178 100644 +--- a/x.c ++++ b/x.c +@@ -105,6 +105,7 @@ typedef struct { + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ ++ int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ + } XWindow; +@@ -243,6 +244,7 @@ static char *usedfont = NULL; + static double usedfontsize = 0; + static double defaultfontsize = 0; + ++static char *opt_alpha = NULL; + static char *opt_class = NULL; + static char **opt_cmd = NULL; + static char *opt_embed = NULL; +@@ -736,7 +738,7 @@ xresize(int col, int row) + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + +@@ -796,6 +798,13 @@ xloadcols(void) + else + die("could not allocate color %d\n", i); + } ++ ++ /* set alpha value of bg color */ ++ if (opt_alpha) ++ alpha = strtof(opt_alpha, NULL); ++ dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); ++ dc.col[defaultbg].pixel &= 0x00FFFFFF; ++ dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; + } + +@@ -1118,11 +1127,23 @@ xinit(int cols, int rows) + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; ++ XWindowAttributes attr; ++ XVisualInfo vis; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); +- xw.vis = XDefaultVisual(xw.dpy, xw.scr); ++ ++ if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { ++ parent = XRootWindow(xw.dpy, xw.scr); ++ xw.depth = 32; ++ } else { ++ XGetWindowAttributes(xw.dpy, parent, &attr); ++ xw.depth = attr.depth; ++ } ++ ++ XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); ++ xw.vis = vis.visual; + + /* font */ + if (!FcInit()) +@@ -1132,7 +1153,7 @@ xinit(int cols, int rows) + xloadfonts(usedfont, 0); + + /* colors */ +- xw.cmap = XDefaultColormap(xw.dpy, xw.scr); ++ xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ +@@ -1152,19 +1173,15 @@ xinit(int cols, int rows) + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + +- if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) +- parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, +- win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, ++ win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; +- dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, +- &gcvalues); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); ++ dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + +@@ -2019,6 +2036,9 @@ main(int argc, char *argv[]) + case 'a': + allowaltscreen = 0; + break; ++ case 'A': ++ opt_alpha = EARGF(usage()); ++ break; + case 'c': + opt_class = EARGF(usage()); + break; diff --git a/st/st-dracula-0.8.5.diff b/st/st-dracula-0.8.5.diff new file mode 100644 index 0000000..b5c3ad3 --- /dev/null +++ b/st/st-dracula-0.8.5.diff @@ -0,0 +1,81 @@ +diff '--color=auto' -up ../st/config.def.h ./config.def.h +--- ../st/config.def.h 2022-03-09 08:28:40.186246176 -0300 ++++ ./config.def.h 2022-03-09 08:26:03.194323581 -0300 +@@ -95,27 +95,29 @@ 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, ++ /* 8 normal colors */ ++ [0] = "#000000", /* black */ ++ [1] = "#ff5555", /* red */ ++ [2] = "#50fa7b", /* green */ ++ [3] = "#f1fa8c", /* yellow */ ++ [4] = "#bd93f9", /* blue */ ++ [5] = "#ff79c6", /* magenta */ ++ [6] = "#8be9fd", /* cyan */ ++ [7] = "#bbbbbb", /* white */ ++ ++ /* 8 bright colors */ ++ [8] = "#44475a", /* black */ ++ [9] = "#ff5555", /* red */ ++ [10] = "#50fa7b", /* green */ ++ [11] = "#f1fa8c", /* yellow */ ++ [12] = "#bd93f9", /* blue */ ++ [13] = "#ff79c6", /* magenta */ ++ [14] = "#8be9fd", /* cyan */ ++ [15] = "#ffffff", /* white */ ++ ++ /* special colors */ ++ [256] = "#282a36", /* background */ ++ [257] = "#f8f8f2", /* foreground */ + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", +@@ -127,14 +129,22 @@ static const char *colorname[] = { + + /* + * Default colors (colorname index) +- * foreground, background, cursor, reverse cursor ++ * foreground, background, cursor + */ +-unsigned int defaultfg = 258; +-unsigned int defaultbg = 259; +-unsigned int defaultcs = 256; ++unsigned int defaultfg = 257; ++unsigned int defaultbg = 256; ++unsigned int defaultcs = 257; + static unsigned int defaultrcs = 257; + + /* ++ * Colors used, when the specific fg == defaultfg. So in reverse mode this ++ * will reverse too. Another logic would only make the simple feature too ++ * complex. ++ */ ++unsigned int defaultitalic = 7; ++unsigned int defaultunderline = 7; ++ ++/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") diff --git a/st/st-scrollback-20210507-4536f46.diff b/st/st-scrollback-20210507-4536f46.diff new file mode 100644 index 0000000..f960f6b --- /dev/null +++ b/st/st-scrollback-20210507-4536f46.diff @@ -0,0 +1,351 @@ +diff --git a/config.def.h b/config.def.h +index 6f05dce..93cbcc0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -199,6 +199,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 ebdf360..817cc47 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; + +@@ -850,6 +857,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); +@@ -1061,13 +1071,53 @@ 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); + +@@ -1077,17 +1127,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); + +@@ -1097,7 +1158,8 @@ tscrollup(int orig, int n) + term.line[i+n] = temp; + } + +- selscroll(orig, -n); ++ if (term.scr == 0) ++ selscroll(orig, -n); + } + + void +@@ -1126,7 +1188,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++; + } +@@ -1291,14 +1353,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 +@@ -1735,11 +1797,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); +@@ -2251,7 +2313,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); + } +@@ -2264,7 +2326,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); + } +@@ -2474,7 +2536,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; +@@ -2511,6 +2573,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)); +@@ -2569,7 +2639,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); + } + } + +@@ -2590,8 +2660,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 fa2eddf..adda2db 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/st-scrollback-mouse-20220127-2c5edf2.diff b/st/st-scrollback-mouse-20220127-2c5edf2.diff new file mode 100644 index 0000000..5c47abc --- /dev/null +++ b/st/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/st.1 b/st/st.1 new file mode 100644 index 0000000..39120b4 --- /dev/null +++ b/st/st.1 @@ -0,0 +1,177 @@ +.TH ST 1 st\-VERSION +.SH NAME +st \- simple terminal +.SH SYNOPSIS +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-l +.IR line ] +.RB [ \-w +.IR windowid ] +.RB [[ \-e ] +.IR command +.RI [ arguments ...]] +.PP +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-w +.IR windowid ] +.RB \-l +.IR line +.RI [ stty_args ...] +.SH DESCRIPTION +.B st +is a simple terminal emulator. +.SH OPTIONS +.TP +.B \-a +disable alternate screens in terminal +.TP +.BI \-c " class" +defines the window class (default $TERM). +.TP +.BI \-f " font" +defines the +.I font +to use when st is run. +.TP +.BI \-g " geometry" +defines the X11 geometry string. +The form is [=][{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..b887ed5 --- /dev/null +++ b/st/st.c @@ -0,0 +1,2727 @@ +/* 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 + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +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); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int, int); +static void tscrolldown(int, int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +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(int y) +{ + int i = term.col; + + if (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup(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) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (term.scr == 0) + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (term.scr == 0) + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert 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 */ + DEFAULT(csiescseq.arg[0], 1); + 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); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + 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], 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 */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete 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 (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +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) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(TLINE(y), x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st/st.c.orig b/st/st.c.orig new file mode 100644 index 0000000..62def59 --- /dev/null +++ b/st/st.c.orig @@ -0,0 +1,2656 @@ +/* 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 + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +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); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +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(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(term.line[*y-1][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(term.line[*y][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &term.line[y][MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup(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) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert 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 */ + DEFAULT(csiescseq.arg[0], 1); + 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); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + 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 */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete 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 (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +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) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st/st.h b/st/st.h new file mode 100644 index 0000000..78762a2 --- /dev/null +++ b/st/st.h @@ -0,0 +1,129 @@ +/* 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_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; +extern unsigned int defaultcs; +extern float alpha; diff --git a/st/st.info b/st/st.info new file mode 100644 index 0000000..8201ad6 --- /dev/null +++ b/st/st.info @@ -0,0 +1,239 @@ +st-mono| simpleterm monocolor, + acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, + am, + bce, + bel=^G, + blink=\E[5m, + bold=\E[1m, + cbt=\E[Z, + cvvis=\E[?25h, + civis=\E[?25l, + clear=\E[H\E[2J, + cnorm=\E[?12l\E[?25h, + colors#2, + cols#80, + cr=^M, + csr=\E[%i%p1%d;%p2%dr, + cub=\E[%p1%dD, + cub1=^H, + cud1=^J, + cud=\E[%p1%dB, + cuf1=\E[C, + cuf=\E[%p1%dC, + cup=\E[%i%p1%d;%p2%dH, + cuu1=\E[A, + cuu=\E[%p1%dA, + dch=\E[%p1%dP, + dch1=\E[P, + dim=\E[2m, + dl=\E[%p1%dM, + dl1=\E[M, + ech=\E[%p1%dX, + ed=\E[J, + el=\E[K, + el1=\E[1K, + enacs=\E)0, + flash=\E[?5h$<80/>\E[?5l, + fsl=^G, + home=\E[H, + hpa=\E[%i%p1%dG, + hs, + ht=^I, + hts=\EH, + ich=\E[%p1%d@, + il1=\E[L, + il=\E[%p1%dL, + ind=^J, + indn=\E[%p1%dS, + invis=\E[8m, + is2=\E[4l\E>\E[?1034l, + it#8, + kel=\E[1;2F, + ked=\E[1;5F, + ka1=\E[1~, + ka3=\E[5~, + kc1=\E[4~, + kc3=\E[6~, + kbs=\177, + kcbt=\E[Z, + kb2=\EOu, + kcub1=\EOD, + kcud1=\EOB, + kcuf1=\EOC, + kcuu1=\EOA, + kDC=\E[3;2~, + kent=\EOM, + kEND=\E[1;2F, + kIC=\E[2;2~, + kNXT=\E[6;2~, + kPRV=\E[5;2~, + kHOM=\E[1;2H, + kLFT=\E[1;2D, + kRIT=\E[1;2C, + kind=\E[1;2B, + kri=\E[1;2A, + kclr=\E[3;5~, + kdl1=\E[3;2~, + kdch1=\E[3~, + kich1=\E[2~, + kend=\E[4~, + kf1=\EOP, + kf2=\EOQ, + kf3=\EOR, + kf4=\EOS, + kf5=\E[15~, + kf6=\E[17~, + kf7=\E[18~, + kf8=\E[19~, + kf9=\E[20~, + kf10=\E[21~, + kf11=\E[23~, + kf12=\E[24~, + kf13=\E[1;2P, + kf14=\E[1;2Q, + kf15=\E[1;2R, + kf16=\E[1;2S, + kf17=\E[15;2~, + kf18=\E[17;2~, + kf19=\E[18;2~, + kf20=\E[19;2~, + kf21=\E[20;2~, + kf22=\E[21;2~, + kf23=\E[23;2~, + kf24=\E[24;2~, + kf25=\E[1;5P, + kf26=\E[1;5Q, + kf27=\E[1;5R, + kf28=\E[1;5S, + kf29=\E[15;5~, + kf30=\E[17;5~, + kf31=\E[18;5~, + kf32=\E[19;5~, + kf33=\E[20;5~, + kf34=\E[21;5~, + kf35=\E[23;5~, + kf36=\E[24;5~, + kf37=\E[1;6P, + kf38=\E[1;6Q, + kf39=\E[1;6R, + kf40=\E[1;6S, + kf41=\E[15;6~, + kf42=\E[17;6~, + kf43=\E[18;6~, + kf44=\E[19;6~, + kf45=\E[20;6~, + kf46=\E[21;6~, + kf47=\E[23;6~, + kf48=\E[24;6~, + kf49=\E[1;3P, + kf50=\E[1;3Q, + kf51=\E[1;3R, + kf52=\E[1;3S, + kf53=\E[15;3~, + kf54=\E[17;3~, + kf55=\E[18;3~, + kf56=\E[19;3~, + kf57=\E[20;3~, + kf58=\E[21;3~, + kf59=\E[23;3~, + kf60=\E[24;3~, + kf61=\E[1;4P, + kf62=\E[1;4Q, + kf63=\E[1;4R, + khome=\E[1~, + kil1=\E[2;5~, + krmir=\E[2;2~, + knp=\E[6~, + kmous=\E[M, + kpp=\E[5~, + lines#24, + mir, + msgr, + npc, + op=\E[39;49m, + pairs#64, + mc0=\E[i, + mc4=\E[4i, + mc5=\E[5i, + rc=\E8, + rev=\E[7m, + ri=\EM, + rin=\E[%p1%dT, + ritm=\E[23m, + rmacs=\E(B, + rmcup=\E[?1049l, + rmir=\E[4l, + rmkx=\E[?1l\E>, + rmso=\E[27m, + rmul=\E[24m, + rs1=\Ec, + rs2=\E[4l\E>\E[?1034l, + sc=\E7, + sitm=\E[3m, + sgr0=\E[0m, + smacs=\E(0, + smcup=\E[?1049h, + smir=\E[4h, + smkx=\E[?1h\E=, + smso=\E[7m, + smul=\E[4m, + tbc=\E[3g, + tsl=\E]0;, + xenl, + vpa=\E[%i%p1%dd, +# XTerm extensions + rmxx=\E[29m, + smxx=\E[9m, +# disabled rep for now: causes some issues with older ncurses versions. +# rep=%p1%c\E[%p2%{1}%-%db, +# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) + Tc, + Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, + +st| simpleterm, + use=st-mono, + colors#8, + setab=\E[4%p1%dm, + setaf=\E[3%p1%dm, + setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, + +st-256color| simpleterm with 256 colors, + use=st, + ccc, + colors#256, + oc=\E]104\007, + pairs#32767, +# Nicked from xterm-256color + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, + +st-meta| simpleterm with meta key, + use=st, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-meta-256color| simpleterm with meta key and 256 colors, + use=st-256color, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-bs| simpleterm with backspace as backspace, + use=st, + kbs=\010, + kdch1=\177, + +st-bs-256color| simpleterm with backspace as backspace and 256colors, + use=st-256color, + kbs=\010, + kdch1=\177, diff --git a/st/st.o b/st/st.o new file mode 100644 index 0000000000000000000000000000000000000000..f637737e4e221c4eec80ed13e1a0ebb7629447cb GIT binary patch literal 80992 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 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..27e81d1 --- /dev/null +++ b/st/x.c @@ -0,0 +1,2116 @@ +/* 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; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_alpha = NULL; +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static uint buttons; /* bit field of pressed buttons */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore 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 && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + + /* set alpha value of bg color */ + if (opt_alpha) + alpha = strtof(opt_alpha, NULL); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { + parent = XRootWindow(xw.dpy, xw.scr); + xw.depth = 32; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.depth = attr.depth; + } + + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); + xw.vis = vis.visual; + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * 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 (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'A': + opt_alpha = EARGF(usage()); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/st/x.c.orig b/st/x.c.orig new file mode 100644 index 0000000..2a3bd38 --- /dev/null +++ b/st/x.c.orig @@ -0,0 +1,2096 @@ +/* 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; +} 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 && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * 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 (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/st/x.o b/st/x.o new file mode 100644 index 0000000000000000000000000000000000000000..d607326a0be8376d09a0f4ba628577ae50cb77c1 GIT binary patch literal 76680 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 z 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:%B %Y}\n{calendar}", + }, + + "cpu": { + "interval": 10, + "format": " {usage}%", // Icon: microchip + "states": { + "warning": 70, + "critical": 90 + }, + "on-click": "swaymsg exec \"\\$term_float_landscape \\$cpu_mon\"" + }, + + "memory": { + "interval": 10, + "format": " {}%", // Icon: memory + "states": { + "warning": 70, + "critical": 90 + }, + "on-click": "swaymsg exec \"\\$term_float_landscape \\$mem_mon\"" + }, + + "network": { + "interval": 30, + "format-wifi": " {ipaddr}", + "format-ethernet": " {ifname}", + "format-disconnected": "⚠ Disconnected", + "tooltip-format": "{ifname}: {ipaddr}", + }, + + "sway/mode": { + "format": "{}", + "tooltip": false + }, + + "sway/window": { + "format": "{}", + "max-length": 120 + }, + +"wlr/workspaces": { + "disable-scroll": true, + "all-outputs": true, +"format": "{icon}", +"format-icons": { + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + }, +"on-scroll-up": "hyprctl dispatch workspace e+1", + "on-scroll-down": "hyprctl dispatch workspace e-1", + "on-click": "activate" +}, + + "tray": { + "icon-size": 21, + "spacing": 5 + } + +} diff --git a/waybar/style.css b/waybar/style.css new file mode 100644 index 0000000..d6054a1 --- /dev/null +++ b/waybar/style.css @@ -0,0 +1,53 @@ +* { + border: none; + border-radius: 0; + font-size: 14px; + min-height: 0; +} + +window#waybar { + background: #000000; + color: #f2d2bd; +} + +#workspaces button { + padding: 0 5px; + background: transparent; + color: #ffffff; + border-bottom: 3px solid transparent; +} +#mode { + background: #64727D; + border-bottom: 3px solid #ffffff; +} + +#clock, #battery, #cpu, #memory, #temperature, #backlight, #network, #pulseaudio, #custom-media, #tray, #mode, #idle_inhibitor { + padding: 0 10px; + margin: 0 5px; +} + +#clock { + color: #f2d2bd; + font-weight: bold; +} + +#battery { + color: #f2d2bd; +} + +#battery.charging { + color: #f2d2bd; +} + +#cpu { + color: #f2d2bd; +} + +#memory { + color: #f2d2bd; +} + +#network { + color: #f2d2bd; +} +