aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBradley Taunt <bt@btxx.org>2024-01-23 15:26:52 -0500
committerBradley Taunt <bt@btxx.org>2024-01-23 15:26:52 -0500
commit0043fe4dc9febc404226b0ff5fc911ea74bd1f38 (patch)
tree8c99fc9da003de0018a3ad94a995471cf82f3881
parentf56fc53c62d1a27c9fc61eaf5b1b6675ee89e79e (diff)
Update install script and include source suckless programs
-rwxr-xr-xdmenuo/LICENSE30
-rwxr-xr-xdmenuo/Makefile64
-rwxr-xr-xdmenuo/README25
-rwxr-xr-xdmenuo/arg.h49
-rwxr-xr-xdmenuo/config.def.h23
-rwxr-xr-xdmenuo/config.h23
-rwxr-xr-xdmenuo/config.mk32
-rwxr-xr-xdmenuo/dmenubin0 -> 47696 bytes
-rwxr-xr-xdmenuo/dmenu.1194
-rwxr-xr-xdmenuo/dmenu.c795
-rw-r--r--dmenuo/dmenu.obin0 -> 31968 bytes
-rwxr-xr-xdmenuo/dmenu_path13
-rwxr-xr-xdmenuo/dmenu_run2
-rwxr-xr-xdmenuo/drw.c450
-rwxr-xr-xdmenuo/drw.h58
-rw-r--r--dmenuo/drw.obin0 -> 11120 bytes
-rwxr-xr-xdmenuo/stestbin0 -> 21368 bytes
-rwxr-xr-xdmenuo/stest.190
-rwxr-xr-xdmenuo/stest.c109
-rw-r--r--dmenuo/stest.obin0 -> 5232 bytes
-rwxr-xr-xdmenuo/util.c36
-rwxr-xr-xdmenuo/util.h8
-rw-r--r--dmenuo/util.obin0 -> 2216 bytes
-rwxr-xr-xdwmo/LICENSE38
-rwxr-xr-xdwmo/Makefile51
-rwxr-xr-xdwmo/README49
-rwxr-xr-xdwmo/config.def.h142
-rwxr-xr-xdwmo/config.h142
-rwxr-xr-xdwmo/config.mk39
-rwxr-xr-xdwmo/drw.c450
-rwxr-xr-xdwmo/drw.h58
-rw-r--r--dwmo/drw.obin0 -> 16880 bytes
-rwxr-xr-xdwmo/dwmbin0 -> 74528 bytes
-rwxr-xr-xdwmo/dwm.1186
-rwxr-xr-xdwmo/dwm.c2196
-rwxr-xr-xdwmo/dwm.c.orig2173
-rw-r--r--dwmo/dwm.obin0 -> 84624 bytes
-rwxr-xr-xdwmo/dwm.pngbin0 -> 373 bytes
-rwxr-xr-xdwmo/patches/dwm-center-6.2.diff90
-rwxr-xr-xdwmo/patches/dwm-hide_vacant_tags-6.3.diff39
-rwxr-xr-xdwmo/patches/dwm-restartsig-20180523-6.2.diff139
-rwxr-xr-xdwmo/transient.c42
-rwxr-xr-xdwmo/util.c36
-rwxr-xr-xdwmo/util.h8
-rw-r--r--dwmo/util.obin0 -> 2592 bytes
-rwxr-xr-xinstall.sh9
-rw-r--r--slocko/LICENSE24
-rw-r--r--slocko/Makefile61
-rw-r--r--slocko/README25
-rw-r--r--slocko/arg.h65
-rw-r--r--slocko/config.def.h12
-rw-r--r--slocko/config.h12
-rw-r--r--slocko/config.mk32
-rw-r--r--slocko/explicit_bzero.c19
-rw-r--r--slocko/explicit_bzero.obin0 -> 1440 bytes
-rwxr-xr-xslocko/slockbin0 -> 18256 bytes
-rw-r--r--slocko/slock.139
-rw-r--r--slocko/slock.c395
-rw-r--r--slocko/slock.obin0 -> 11248 bytes
-rw-r--r--slocko/util.h2
-rwxr-xr-xslstatuso/LICENSE41
-rwxr-xr-xslstatuso/Makefile68
-rwxr-xr-xslstatuso/README71
-rwxr-xr-xslstatuso/arg.h33
-rwxr-xr-xslstatuso/components/battery.c244
-rw-r--r--slstatuso/components/battery.obin0 -> 4208 bytes
-rwxr-xr-xslstatuso/components/cat.c32
-rw-r--r--slstatuso/components/cat.obin0 -> 2216 bytes
-rwxr-xr-xslstatuso/components/cpu.c157
-rw-r--r--slstatuso/components/cpu.obin0 -> 3400 bytes
-rwxr-xr-xslstatuso/components/datetime.c20
-rw-r--r--slstatuso/components/datetime.obin0 -> 1984 bytes
-rwxr-xr-xslstatuso/components/disk.c59
-rw-r--r--slstatuso/components/disk.obin0 -> 3992 bytes
-rwxr-xr-xslstatuso/components/entropy.c28
-rw-r--r--slstatuso/components/entropy.obin0 -> 1584 bytes
-rwxr-xr-xslstatuso/components/hostname.c17
-rw-r--r--slstatuso/components/hostname.obin0 -> 1808 bytes
-rwxr-xr-xslstatuso/components/ip.c61
-rw-r--r--slstatuso/components/ip.obin0 -> 3072 bytes
-rwxr-xr-xslstatuso/components/kernel_release.c19
-rw-r--r--slstatuso/components/kernel_release.obin0 -> 1912 bytes
-rwxr-xr-xslstatuso/components/keyboard_indicators.c50
-rw-r--r--slstatuso/components/keyboard_indicators.obin0 -> 2440 bytes
-rwxr-xr-xslstatuso/components/keymap.c85
-rw-r--r--slstatuso/components/keymap.obin0 -> 4064 bytes
-rwxr-xr-xslstatuso/components/load_avg.c19
-rw-r--r--slstatuso/components/load_avg.obin0 -> 1952 bytes
-rwxr-xr-xslstatuso/components/netspeeds.c129
-rw-r--r--slstatuso/components/netspeeds.obin0 -> 3576 bytes
-rwxr-xr-xslstatuso/components/num_files.c32
-rw-r--r--slstatuso/components/num_files.obin0 -> 2376 bytes
-rwxr-xr-xslstatuso/components/ram.c212
-rw-r--r--slstatuso/components/ram.obin0 -> 3544 bytes
-rwxr-xr-xslstatuso/components/run_command.c31
-rw-r--r--slstatuso/components/run_command.obin0 -> 2232 bytes
-rwxr-xr-xslstatuso/components/swap.c274
-rw-r--r--slstatuso/components/swap.obin0 -> 4336 bytes
-rwxr-xr-xslstatuso/components/temperature.c73
-rw-r--r--slstatuso/components/temperature.obin0 -> 2280 bytes
-rwxr-xr-xslstatuso/components/uptime.c34
-rw-r--r--slstatuso/components/uptime.obin0 -> 2088 bytes
-rwxr-xr-xslstatuso/components/user.c33
-rw-r--r--slstatuso/components/user.obin0 -> 2840 bytes
-rwxr-xr-xslstatuso/components/volume.c219
-rw-r--r--slstatuso/components/volume.obin0 -> 6136 bytes
-rwxr-xr-xslstatuso/components/wifi.c267
-rw-r--r--slstatuso/components/wifi.obin0 -> 3864 bytes
-rwxr-xr-xslstatuso/config.def.h77
-rwxr-xr-xslstatuso/config.h77
-rwxr-xr-xslstatuso/config.mk22
-rwxr-xr-xslstatuso/slstatusbin0 -> 29872 bytes
-rwxr-xr-xslstatuso/slstatus.128
-rwxr-xr-xslstatuso/slstatus.c132
-rwxr-xr-xslstatuso/slstatus.h84
-rw-r--r--slstatuso/slstatus.obin0 -> 7096 bytes
-rwxr-xr-xslstatuso/util.c144
-rwxr-xr-xslstatuso/util.h16
-rw-r--r--slstatuso/util.obin0 -> 7968 bytes
-rwxr-xr-xsto/FAQ253
-rwxr-xr-xsto/LEGACY17
-rwxr-xr-xsto/LICENSE34
-rwxr-xr-xsto/Makefile57
-rwxr-xr-xsto/README35
-rwxr-xr-xsto/TODO28
-rwxr-xr-xsto/arg.h50
-rwxr-xr-xsto/config.def.h476
-rwxr-xr-xsto/config.h476
-rwxr-xr-xsto/config.mk36
-rwxr-xr-xsto/patches/st-scrollback-0.8.5.diff350
-rwxr-xr-xsto/stbin0 -> 110632 bytes
-rwxr-xr-xsto/st.1177
-rwxr-xr-xsto/st.c2727
-rwxr-xr-xsto/st.c.orig2656
-rwxr-xr-xsto/st.h128
-rwxr-xr-xsto/st.info239
-rw-r--r--sto/st.obin0 -> 80992 bytes
-rwxr-xr-xsto/win.h41
-rwxr-xr-xsto/x.c2099
-rw-r--r--sto/x.obin0 -> 75024 bytes
140 files changed, 21332 insertions, 9 deletions
diff --git a/dmenuo/LICENSE b/dmenuo/LICENSE
new file mode 100755
index 0000000..2a64b28
--- /dev/null
+++ b/dmenuo/LICENSE
@@ -0,0 +1,30 @@
+MIT/X Consortium License
+
+© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
+© 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com>
+© 2006-2007 Michał Janeczek <janeczek@gmail.com>
+© 2007 Kris Maglione <jg@suckless.org>
+© 2009 Gottox <gottox@s01.de>
+© 2009 Markus Schnalke <meillo@marmaro.de>
+© 2009 Evan Gates <evan.gates@gmail.com>
+© 2010-2012 Connor Lane Smith <cls@lubutu.com>
+© 2014-2022 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2015-2019 Quentin Rameau <quinq@fifth.space>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/dmenuo/Makefile b/dmenuo/Makefile
new file mode 100755
index 0000000..a03a95c
--- /dev/null
+++ b/dmenuo/Makefile
@@ -0,0 +1,64 @@
+# dmenu - dynamic menu
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = drw.c dmenu.c stest.c util.c
+OBJ = $(SRC:.c=.o)
+
+all: options dmenu stest
+
+options:
+ @echo dmenu build options:
+ @echo "CFLAGS = $(CFLAGS)"
+ @echo "LDFLAGS = $(LDFLAGS)"
+ @echo "CC = $(CC)"
+
+.c.o:
+ $(CC) -c $(CFLAGS) $<
+
+config.h:
+ cp config.def.h $@
+
+$(OBJ): arg.h config.h config.mk drw.h
+
+dmenu: dmenu.o drw.o util.o
+ $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS)
+
+stest: stest.o
+ $(CC) -o $@ stest.o $(LDFLAGS)
+
+clean:
+ rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz
+
+dist: clean
+ mkdir -p dmenu-$(VERSION)
+ cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\
+ drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\
+ dmenu-$(VERSION)
+ tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION)
+ gzip dmenu-$(VERSION).tar
+ rm -rf dmenu-$(VERSION)
+
+install: all
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/stest
+ mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1
+ sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1
+ chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1
+ chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1
+
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\
+ $(DESTDIR)$(PREFIX)/bin/dmenu_path\
+ $(DESTDIR)$(PREFIX)/bin/dmenu_run\
+ $(DESTDIR)$(PREFIX)/bin/stest\
+ $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\
+ $(DESTDIR)$(MANPREFIX)/man1/stest.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/dmenuo/README b/dmenuo/README
new file mode 100755
index 0000000..6120820
--- /dev/null
+++ b/dmenuo/README
@@ -0,0 +1,25 @@
+dmenuo - dynamic menu for openbsd
+=================================
+
+dmenu is an efficient dynamic menu for X.
+
+
+Requirements
+------------
+In order to build dmenu you need the Xlib header files.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (dmenu is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install dmenu
+(if necessary as root):
+
+ make clean install
+
+
+Running dmenu
+-------------
+See the man page for details.
diff --git a/dmenuo/arg.h b/dmenuo/arg.h
new file mode 100755
index 0000000..e94e02b
--- /dev/null
+++ b/dmenuo/arg.h
@@ -0,0 +1,49 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][0] == '-'\
+ && argv[0][1];\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/dmenuo/config.def.h b/dmenuo/config.def.h
new file mode 100755
index 0000000..1edb647
--- /dev/null
+++ b/dmenuo/config.def.h
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+/* Default settings; can be overriden by command line. */
+
+static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
+/* -fn option overrides fonts[0]; default X11 font or font set */
+static const char *fonts[] = {
+ "monospace:size=10"
+};
+static const char *prompt = NULL; /* -p option; prompt to the left of input field */
+static const char *colors[SchemeLast][2] = {
+ /* fg bg */
+ [SchemeNorm] = { "#bbbbbb", "#222222" },
+ [SchemeSel] = { "#eeeeee", "#005577" },
+ [SchemeOut] = { "#000000", "#00ffff" },
+};
+/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
+static unsigned int lines = 0;
+
+/*
+ * Characters not considered part of a word while deleting words
+ * for example: " /?\"&[]"
+ */
+static const char worddelimiters[] = " ";
diff --git a/dmenuo/config.h b/dmenuo/config.h
new file mode 100755
index 0000000..1edb647
--- /dev/null
+++ b/dmenuo/config.h
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+/* Default settings; can be overriden by command line. */
+
+static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
+/* -fn option overrides fonts[0]; default X11 font or font set */
+static const char *fonts[] = {
+ "monospace:size=10"
+};
+static const char *prompt = NULL; /* -p option; prompt to the left of input field */
+static const char *colors[SchemeLast][2] = {
+ /* fg bg */
+ [SchemeNorm] = { "#bbbbbb", "#222222" },
+ [SchemeSel] = { "#eeeeee", "#005577" },
+ [SchemeOut] = { "#000000", "#00ffff" },
+};
+/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
+static unsigned int lines = 0;
+
+/*
+ * Characters not considered part of a word while deleting words
+ * for example: " /?\"&[]"
+ */
+static const char worddelimiters[] = " ";
diff --git a/dmenuo/config.mk b/dmenuo/config.mk
new file mode 100755
index 0000000..0633398
--- /dev/null
+++ b/dmenuo/config.mk
@@ -0,0 +1,32 @@
+# dmenu version
+VERSION = 5.2
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+
+# Xinerama, comment if you don't want it
+XINERAMALIBS = -lXinerama
+XINERAMAFLAGS = -DXINERAMA
+
+# freetype
+FREETYPELIBS = -lfontconfig -lXft
+FREETYPEINC = /usr/include/freetype2
+# OpenBSD (uncomment)
+FREETYPEINC = $(X11INC)/freetype2
+MANPREFIX = ${PREFIX}/man
+
+# includes and libs
+INCS = -I$(X11INC) -I$(FREETYPEINC)
+LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS)
+
+# flags
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
+CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS)
+LDFLAGS = $(LIBS)
+
+# compiler and linker
+CC = cc
diff --git a/dmenuo/dmenu b/dmenuo/dmenu
new file mode 100755
index 0000000..8e14a8c
--- /dev/null
+++ b/dmenuo/dmenu
Binary files differ
diff --git a/dmenuo/dmenu.1 b/dmenuo/dmenu.1
new file mode 100755
index 0000000..323f93c
--- /dev/null
+++ b/dmenuo/dmenu.1
@@ -0,0 +1,194 @@
+.TH DMENU 1 dmenu\-VERSION
+.SH NAME
+dmenu \- dynamic menu
+.SH SYNOPSIS
+.B dmenu
+.RB [ \-bfiv ]
+.RB [ \-l
+.IR lines ]
+.RB [ \-m
+.IR monitor ]
+.RB [ \-p
+.IR prompt ]
+.RB [ \-fn
+.IR font ]
+.RB [ \-nb
+.IR color ]
+.RB [ \-nf
+.IR color ]
+.RB [ \-sb
+.IR color ]
+.RB [ \-sf
+.IR color ]
+.RB [ \-w
+.IR windowid ]
+.P
+.BR dmenu_run " ..."
+.SH DESCRIPTION
+.B dmenu
+is a dynamic menu for X, which reads a list of newline\-separated items from
+stdin. When the user selects an item and presses Return, their choice is printed
+to stdout and dmenu terminates. Entering text will narrow the items to those
+matching the tokens in the input.
+.P
+.B dmenu_run
+is a script used by
+.IR dwm (1)
+which lists programs in the user's $PATH and runs the result in their $SHELL.
+.SH OPTIONS
+.TP
+.B \-b
+dmenu appears at the bottom of the screen.
+.TP
+.B \-f
+dmenu grabs the keyboard before reading stdin if not reading from a tty. This
+is faster, but will lock up X until stdin reaches end\-of\-file.
+.TP
+.B \-i
+dmenu matches menu items case insensitively.
+.TP
+.BI \-l " lines"
+dmenu lists items vertically, with the given number of lines.
+.TP
+.BI \-m " monitor"
+dmenu is displayed on the monitor number supplied. Monitor numbers are starting
+from 0.
+.TP
+.BI \-p " prompt"
+defines the prompt to be displayed to the left of the input field.
+.TP
+.BI \-fn " font"
+defines the font or font set used.
+.TP
+.BI \-nb " color"
+defines the normal background color.
+.IR #RGB ,
+.IR #RRGGBB ,
+and X color names are supported.
+.TP
+.BI \-nf " color"
+defines the normal foreground color.
+.TP
+.BI \-sb " color"
+defines the selected background color.
+.TP
+.BI \-sf " color"
+defines the selected foreground color.
+.TP
+.B \-v
+prints version information to stdout, then exits.
+.TP
+.BI \-w " windowid"
+embed into windowid.
+.SH USAGE
+dmenu is completely controlled by the keyboard. Items are selected using the
+arrow keys, page up, page down, home, and end.
+.TP
+.B Tab
+Copy the selected item to the input field.
+.TP
+.B Return
+Confirm selection. Prints the selected item to stdout and exits, returning
+success.
+.TP
+.B Ctrl-Return
+Confirm selection. Prints the selected item to stdout and continues.
+.TP
+.B Shift\-Return
+Confirm input. Prints the input text to stdout and exits, returning success.
+.TP
+.B Escape
+Exit without selecting an item, returning failure.
+.TP
+.B Ctrl-Left
+Move cursor to the start of the current word
+.TP
+.B Ctrl-Right
+Move cursor to the end of the current word
+.TP
+.B C\-a
+Home
+.TP
+.B C\-b
+Left
+.TP
+.B C\-c
+Escape
+.TP
+.B C\-d
+Delete
+.TP
+.B C\-e
+End
+.TP
+.B C\-f
+Right
+.TP
+.B C\-g
+Escape
+.TP
+.B C\-h
+Backspace
+.TP
+.B C\-i
+Tab
+.TP
+.B C\-j
+Return
+.TP
+.B C\-J
+Shift-Return
+.TP
+.B C\-k
+Delete line right
+.TP
+.B C\-m
+Return
+.TP
+.B C\-M
+Shift-Return
+.TP
+.B C\-n
+Down
+.TP
+.B C\-p
+Up
+.TP
+.B C\-u
+Delete line left
+.TP
+.B C\-w
+Delete word left
+.TP
+.B C\-y
+Paste from primary X selection
+.TP
+.B C\-Y
+Paste from X clipboard
+.TP
+.B M\-b
+Move cursor to the start of the current word
+.TP
+.B M\-f
+Move cursor to the end of the current word
+.TP
+.B M\-g
+Home
+.TP
+.B M\-G
+End
+.TP
+.B M\-h
+Up
+.TP
+.B M\-j
+Page down
+.TP
+.B M\-k
+Page up
+.TP
+.B M\-l
+Down
+.SH SEE ALSO
+.IR dwm (1),
+.IR stest (1)
diff --git a/dmenuo/dmenu.c b/dmenuo/dmenu.c
new file mode 100755
index 0000000..27b7a30
--- /dev/null
+++ b/dmenuo/dmenu.c
@@ -0,0 +1,795 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#ifdef XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+#include "util.h"
+
+/* macros */
+#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \
+ * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
+#define LENGTH(X) (sizeof X / sizeof X[0])
+#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
+/* enums */
+enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
+
+struct item {
+ char *text;
+ struct item *left, *right;
+ int out;
+};
+
+static char text[BUFSIZ] = "";
+static char *embed;
+static int bh, mw, mh;
+static int inputw = 0, promptw;
+static int lrpad; /* sum of left and right padding */
+static size_t cursor;
+static struct item *items = NULL;
+static struct item *matches, *matchend;
+static struct item *prev, *curr, *next, *sel;
+static int mon = -1, screen;
+
+static Atom clip, utf8;
+static Display *dpy;
+static Window root, parentwin, win;
+static XIC xic;
+
+static Drw *drw;
+static Clr *scheme[SchemeLast];
+
+#include "config.h"
+
+static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+static char *(*fstrstr)(const char *, const char *) = strstr;
+
+static unsigned int
+textw_clamp(const char *str, unsigned int n)
+{
+ unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
+ return MIN(w, n);
+}
+
+static void
+appenditem(struct item *item, struct item **list, struct item **last)
+{
+ if (*last)
+ (*last)->right = item;
+ else
+ *list = item;
+
+ item->left = *last;
+ item->right = NULL;
+ *last = item;
+}
+
+static void
+calcoffsets(void)
+{
+ int i, n;
+
+ if (lines > 0)
+ n = lines * bh;
+ else
+ n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
+ /* calculate which items will begin the next page and previous page */
+ for (i = 0, next = curr; next; next = next->right)
+ if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n)
+ break;
+ for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
+ if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n)
+ break;
+}
+
+static void
+cleanup(void)
+{
+ size_t i;
+
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ for (i = 0; i < SchemeLast; i++)
+ free(scheme[i]);
+ for (i = 0; items && items[i].text; ++i)
+ free(items[i].text);
+ free(items);
+ drw_free(drw);
+ XSync(dpy, False);
+ XCloseDisplay(dpy);
+}
+
+static char *
+cistrstr(const char *h, const char *n)
+{
+ size_t i;
+
+ if (!n[0])
+ return (char *)h;
+
+ for (; *h; ++h) {
+ for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
+ tolower((unsigned char)h[i]); ++i)
+ ;
+ if (n[i] == '\0')
+ return (char *)h;
+ }
+ return NULL;
+}
+
+static int
+drawitem(struct item *item, int x, int y, int w)
+{
+ if (item == sel)
+ drw_setscheme(drw, scheme[SchemeSel]);
+ else if (item->out)
+ drw_setscheme(drw, scheme[SchemeOut]);
+ else
+ drw_setscheme(drw, scheme[SchemeNorm]);
+
+ return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
+}
+
+static void
+drawmenu(void)
+{
+ unsigned int curpos;
+ struct item *item;
+ int x = 0, y = 0, w;
+
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_rect(drw, 0, 0, mw, mh, 1, 1);
+
+ if (prompt && *prompt) {
+ drw_setscheme(drw, scheme[SchemeSel]);
+ x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
+ }
+ /* draw input field */
+ w = (lines > 0 || !matches) ? mw - x : inputw;
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
+
+ curpos = TEXTW(text) - TEXTW(&text[cursor]);
+ if ((curpos += lrpad / 2 - 1) < w) {
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
+ }
+
+ if (lines > 0) {
+ /* draw vertical list */
+ for (item = curr; item != next; item = item->right)
+ drawitem(item, x, y += bh, mw - x);
+ } else if (matches) {
+ /* draw horizontal list */
+ x += inputw;
+ w = TEXTW("<");
+ if (curr->left) {
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0);
+ }
+ x += w;
+ for (item = curr; item != next; item = item->right)
+ x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">")));
+ if (next) {
+ w = TEXTW(">");
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
+ }
+ }
+ drw_map(drw, win, 0, 0, mw, mh);
+}
+
+static void
+grabfocus(void)
+{
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 };
+ Window focuswin;
+ int i, revertwin;
+
+ for (i = 0; i < 100; ++i) {
+ XGetInputFocus(dpy, &focuswin, &revertwin);
+ if (focuswin == win)
+ return;
+ XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
+ nanosleep(&ts, NULL);
+ }
+ die("cannot grab focus");
+}
+
+static void
+grabkeyboard(void)
+{
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
+ int i;
+
+ if (embed)
+ return;
+ /* try to grab keyboard, we may have to wait for another process to ungrab */
+ for (i = 0; i < 1000; i++) {
+ if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync,
+ GrabModeAsync, CurrentTime) == GrabSuccess)
+ return;
+ nanosleep(&ts, NULL);
+ }
+ die("cannot grab keyboard");
+}
+
+static void
+match(void)
+{
+ static char **tokv = NULL;
+ static int tokn = 0;
+
+ char buf[sizeof text], *s;
+ int i, tokc = 0;
+ size_t len, textsize;
+ struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
+
+ strcpy(buf, text);
+ /* separate input text into tokens to be matched individually */
+ for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
+ if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
+ die("cannot realloc %zu bytes:", tokn * sizeof *tokv);
+ len = tokc ? strlen(tokv[0]) : 0;
+
+ matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
+ textsize = strlen(text) + 1;
+ for (item = items; item && item->text; item++) {
+ for (i = 0; i < tokc; i++)
+ if (!fstrstr(item->text, tokv[i]))
+ break;
+ if (i != tokc) /* not all tokens match */
+ continue;
+ /* exact matches go first, then prefixes, then substrings */
+ if (!tokc || !fstrncmp(text, item->text, textsize))
+ appenditem(item, &matches, &matchend);
+ else if (!fstrncmp(tokv[0], item->text, len))
+ appenditem(item, &lprefix, &prefixend);
+ else
+ appenditem(item, &lsubstr, &substrend);
+ }
+ if (lprefix) {
+ if (matches) {
+ matchend->right = lprefix;
+ lprefix->left = matchend;
+ } else
+ matches = lprefix;
+ matchend = prefixend;
+ }
+ if (lsubstr) {
+ if (matches) {
+ matchend->right = lsubstr;
+ lsubstr->left = matchend;
+ } else
+ matches = lsubstr;
+ matchend = substrend;
+ }
+ curr = sel = matches;
+ calcoffsets();
+}
+
+static void
+insert(const char *str, ssize_t n)
+{
+ if (strlen(text) + n > sizeof text - 1)
+ return;
+ /* move existing text out of the way, insert new text, and update cursor */
+ memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
+ if (n > 0)
+ memcpy(&text[cursor], str, n);
+ cursor += n;
+ match();
+}
+
+static size_t
+nextrune(int inc)
+{
+ ssize_t n;
+
+ /* return location of next utf8 rune in the given direction (+1 or -1) */
+ for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
+ ;
+ return n;
+}
+
+static void
+movewordedge(int dir)
+{
+ if (dir < 0) { /* move cursor to the start of the word*/
+ while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
+ cursor = nextrune(-1);
+ while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
+ cursor = nextrune(-1);
+ } else { /* move cursor to the end of the word */
+ while (text[cursor] && strchr(worddelimiters, text[cursor]))
+ cursor = nextrune(+1);
+ while (text[cursor] && !strchr(worddelimiters, text[cursor]))
+ cursor = nextrune(+1);
+ }
+}
+
+static void
+keypress(XKeyEvent *ev)
+{
+ char buf[64];
+ int len;
+ KeySym ksym = NoSymbol;
+ Status status;
+
+ len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
+ switch (status) {
+ default: /* XLookupNone, XBufferOverflow */
+ return;
+ case XLookupChars: /* composed string from input method */
+ goto insert;
+ case XLookupKeySym:
+ case XLookupBoth: /* a KeySym and a string are returned: use keysym */
+ break;
+ }
+
+ if (ev->state & ControlMask) {
+ switch(ksym) {
+ case XK_a: ksym = XK_Home; break;
+ case XK_b: ksym = XK_Left; break;
+ case XK_c: ksym = XK_Escape; break;
+ case XK_d: ksym = XK_Delete; break;
+ case XK_e: ksym = XK_End; break;
+ case XK_f: ksym = XK_Right; break;
+ case XK_g: ksym = XK_Escape; break;
+ case XK_h: ksym = XK_BackSpace; break;
+ case XK_i: ksym = XK_Tab; break;
+ case XK_j: /* fallthrough */
+ case XK_J: /* fallthrough */
+ case XK_m: /* fallthrough */
+ case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break;
+ case XK_n: ksym = XK_Down; break;
+ case XK_p: ksym = XK_Up; break;
+
+ case XK_k: /* delete right */
+ text[cursor] = '\0';
+ match();
+ break;
+ case XK_u: /* delete left */
+ insert(NULL, 0 - cursor);
+ break;
+ case XK_w: /* delete word */
+ while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
+ insert(NULL, nextrune(-1) - cursor);
+ while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
+ insert(NULL, nextrune(-1) - cursor);
+ break;
+ case XK_y: /* paste selection */
+ case XK_Y:
+ XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
+ utf8, utf8, win, CurrentTime);
+ return;
+ case XK_Left:
+ case XK_KP_Left:
+ movewordedge(-1);
+ goto draw;
+ case XK_Right:
+ case XK_KP_Right:
+ movewordedge(+1);
+ goto draw;
+ case XK_Return:
+ case XK_KP_Enter:
+ break;
+ case XK_bracketleft:
+ cleanup();
+ exit(1);
+ default:
+ return;
+ }
+ } else if (ev->state & Mod1Mask) {
+ switch(ksym) {
+ case XK_b:
+ movewordedge(-1);
+ goto draw;
+ case XK_f:
+ movewordedge(+1);
+ goto draw;
+ case XK_g: ksym = XK_Home; break;
+ case XK_G: ksym = XK_End; break;
+ case XK_h: ksym = XK_Up; break;
+ case XK_j: ksym = XK_Next; break;
+ case XK_k: ksym = XK_Prior; break;
+ case XK_l: ksym = XK_Down; break;
+ default:
+ return;
+ }
+ }
+
+ switch(ksym) {
+ default:
+insert:
+ if (!iscntrl((unsigned char)*buf))
+ insert(buf, len);
+ break;
+ case XK_Delete:
+ case XK_KP_Delete:
+ if (text[cursor] == '\0')
+ return;
+ cursor = nextrune(+1);
+ /* fallthrough */
+ case XK_BackSpace:
+ if (cursor == 0)
+ return;
+ insert(NULL, nextrune(-1) - cursor);
+ break;
+ case XK_End:
+ case XK_KP_End:
+ if (text[cursor] != '\0') {
+ cursor = strlen(text);
+ break;
+ }
+ if (next) {
+ /* jump to end of list and position items in reverse */
+ curr = matchend;
+ calcoffsets();
+ curr = prev;
+ calcoffsets();
+ while (next && (curr = curr->right))
+ calcoffsets();
+ }
+ sel = matchend;
+ break;
+ case XK_Escape:
+ cleanup();
+ exit(1);
+ case XK_Home:
+ case XK_KP_Home:
+ if (sel == matches) {
+ cursor = 0;
+ break;
+ }
+ sel = curr = matches;
+ calcoffsets();
+ break;
+ case XK_Left:
+ case XK_KP_Left:
+ if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
+ cursor = nextrune(-1);
+ break;
+ }
+ if (lines > 0)
+ return;
+ /* fallthrough */
+ case XK_Up:
+ case XK_KP_Up:
+ if (sel && sel->left && (sel = sel->left)->right == curr) {
+ curr = prev;
+ calcoffsets();
+ }
+ break;
+ case XK_Next:
+ case XK_KP_Next:
+ if (!next)
+ return;
+ sel = curr = next;
+ calcoffsets();
+ break;
+ case XK_Prior:
+ case XK_KP_Prior:
+ if (!prev)
+ return;
+ sel = curr = prev;
+ calcoffsets();
+ break;
+ case XK_Return:
+ case XK_KP_Enter:
+ puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
+ if (!(ev->state & ControlMask)) {
+ cleanup();
+ exit(0);
+ }
+ if (sel)
+ sel->out = 1;
+ break;
+ case XK_Right:
+ case XK_KP_Right:
+ if (text[cursor] != '\0') {
+ cursor = nextrune(+1);
+ break;
+ }
+ if (lines > 0)
+ return;
+ /* fallthrough */
+ case XK_Down:
+ case XK_KP_Down:
+ if (sel && sel->right && (sel = sel->right) == next) {
+ curr = next;
+ calcoffsets();
+ }
+ break;
+ case XK_Tab:
+ if (!sel)
+ return;
+ cursor = strnlen(sel->text, sizeof text - 1);
+ memcpy(text, sel->text, cursor);
+ text[cursor] = '\0';
+ match();
+ break;
+ }
+
+draw:
+ drawmenu();
+}
+
+static void
+paste(void)
+{
+ char *p, *q;
+ int di;
+ unsigned long dl;
+ Atom da;
+
+ /* we have been given the current selection, now insert it into input */
+ if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
+ utf8, &da, &di, &dl, &dl, (unsigned char **)&p)
+ == Success && p) {
+ insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
+ XFree(p);
+ }
+ drawmenu();
+}
+
+static void
+readstdin(void)
+{
+ char *line = NULL;
+ size_t i, junk, itemsiz = 0;
+ ssize_t len;
+
+ /* read each line from stdin and add it to the item list */
+ for (i = 0; (len = getline(&line, &junk, stdin)) != -1; i++) {
+ if (i + 1 >= itemsiz) {
+ itemsiz += 256;
+ if (!(items = realloc(items, itemsiz * sizeof(*items))))
+ die("cannot realloc %zu bytes:", itemsiz * sizeof(*items));
+ }
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ items[i].text = line;
+ items[i].out = 0;
+ line = NULL; /* next call of getline() allocates a new line */
+ }
+ free(line);
+ if (items)
+ items[i].text = NULL;
+ lines = MIN(lines, i);
+}
+
+static void
+run(void)
+{
+ XEvent ev;
+
+ while (!XNextEvent(dpy, &ev)) {
+ if (XFilterEvent(&ev, win))
+ continue;
+ switch(ev.type) {
+ case DestroyNotify:
+ if (ev.xdestroywindow.window != win)
+ break;
+ cleanup();
+ exit(1);
+ case Expose:
+ if (ev.xexpose.count == 0)
+ drw_map(drw, win, 0, 0, mw, mh);
+ break;
+ case FocusIn:
+ /* regrab focus from parent window */
+ if (ev.xfocus.window != win)
+ grabfocus();
+ break;
+ case KeyPress:
+ keypress(&ev.xkey);
+ break;
+ case SelectionNotify:
+ if (ev.xselection.property == utf8)
+ paste();
+ break;
+ case VisibilityNotify:
+ if (ev.xvisibility.state != VisibilityUnobscured)
+ XRaiseWindow(dpy, win);
+ break;
+ }
+ }
+}
+
+static void
+setup(void)
+{
+ int x, y, i, j;
+ unsigned int du;
+ XSetWindowAttributes swa;
+ XIM xim;
+ Window w, dw, *dws;
+ XWindowAttributes wa;
+ XClassHint ch = {"dmenu", "dmenu"};
+#ifdef XINERAMA
+ XineramaScreenInfo *info;
+ Window pw;
+ int a, di, n, area = 0;
+#endif
+ /* init appearance */
+ for (j = 0; j < SchemeLast; j++)
+ scheme[j] = drw_scm_create(drw, colors[j], 2);
+
+ clip = XInternAtom(dpy, "CLIPBOARD", False);
+ utf8 = XInternAtom(dpy, "UTF8_STRING", False);
+
+ /* calculate menu geometry */
+ bh = drw->fonts->h + 2;
+ lines = MAX(lines, 0);
+ mh = (lines + 1) * bh;
+#ifdef XINERAMA
+ i = 0;
+ if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
+ XGetInputFocus(dpy, &w, &di);
+ if (mon >= 0 && mon < n)
+ i = mon;
+ else if (w != root && w != PointerRoot && w != None) {
+ /* find top-level window containing current input focus */
+ do {
+ if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws)
+ XFree(dws);
+ } while (w != root && w != pw);
+ /* find xinerama screen with which the window intersects most */
+ if (XGetWindowAttributes(dpy, pw, &wa))
+ for (j = 0; j < n; j++)
+ if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) {
+ area = a;
+ i = j;
+ }
+ }
+ /* no focused window is on screen, so use pointer location instead */
+ if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
+ for (i = 0; i < n; i++)
+ if (INTERSECT(x, y, 1, 1, info[i]) != 0)
+ break;
+
+ x = info[i].x_org;
+ y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
+ mw = info[i].width;
+ XFree(info);
+ } else
+#endif
+ {
+ if (!XGetWindowAttributes(dpy, parentwin, &wa))
+ die("could not get embedding window attributes: 0x%lx",
+ parentwin);
+ x = 0;
+ y = topbar ? 0 : wa.height - mh;
+ mw = wa.width;
+ }
+ promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
+ inputw = mw / 3; /* input width: ~33% of monitor width */
+ match();
+
+ /* create menu window */
+ swa.override_redirect = True;
+ swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
+ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
+ win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
+ CopyFromParent, CopyFromParent, CopyFromParent,
+ CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
+ XSetClassHint(dpy, win, &ch);
+
+
+ /* input methods */
+ if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
+ die("XOpenIM failed: could not open input device");
+
+ xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
+ XNClientWindow, win, XNFocusWindow, win, NULL);
+
+ XMapRaised(dpy, win);
+ if (embed) {
+ XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
+ if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
+ for (i = 0; i < du && dws[i] != win; ++i)
+ XSelectInput(dpy, dws[i], FocusChangeMask);
+ XFree(dws);
+ }
+ grabfocus();
+ }
+ drw_resize(drw, mw, mh);
+ drawmenu();
+}
+
+static void
+usage(void)
+{
+ die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
+ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
+}
+
+int
+main(int argc, char *argv[])
+{
+ XWindowAttributes wa;
+ int i, fast = 0;
+
+ for (i = 1; i < argc; i++)
+ /* these options take no arguments */
+ if (!strcmp(argv[i], "-v")) { /* prints version information */
+ puts("dmenu-"VERSION);
+ exit(0);
+ } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
+ topbar = 0;
+ else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
+ fast = 1;
+ else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
+ fstrncmp = strncasecmp;
+ fstrstr = cistrstr;
+ } else if (i + 1 == argc)
+ usage();
+ /* these options take one argument */
+ else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */
+ lines = atoi(argv[++i]);
+ else if (!strcmp(argv[i], "-m"))
+ mon = atoi(argv[++i]);
+ else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */
+ prompt = argv[++i];
+ else if (!strcmp(argv[i], "-fn")) /* font or font set */
+ fonts[0] = argv[++i];
+ else if (!strcmp(argv[i], "-nb")) /* normal background color */
+ colors[SchemeNorm][ColBg] = argv[++i];
+ else if (!strcmp(argv[i], "-nf")) /* normal foreground color */
+ colors[SchemeNorm][ColFg] = argv[++i];
+ else if (!strcmp(argv[i], "-sb")) /* selected background color */
+ colors[SchemeSel][ColBg] = argv[++i];
+ else if (!strcmp(argv[i], "-sf")) /* selected foreground color */
+ colors[SchemeSel][ColFg] = argv[++i];
+ else if (!strcmp(argv[i], "-w")) /* embedding window id */
+ embed = argv[++i];
+ else
+ usage();
+
+ if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
+ fputs("warning: no locale support\n", stderr);
+ if (!(dpy = XOpenDisplay(NULL)))
+ die("cannot open display");
+ screen = DefaultScreen(dpy);
+ root = RootWindow(dpy, screen);
+ if (!embed || !(parentwin = strtol(embed, NULL, 0)))
+ parentwin = root;
+ if (!XGetWindowAttributes(dpy, parentwin, &wa))
+ die("could not get embedding window attributes: 0x%lx",
+ parentwin);
+ drw = drw_create(dpy, screen, root, wa.width, wa.height);
+ if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath", NULL) == -1)
+ die("pledge");
+#endif
+
+ if (fast && !isatty(0)) {
+ grabkeyboard();
+ readstdin();
+ } else {
+ readstdin();
+ grabkeyboard();
+ }
+ setup();
+ run();
+
+ return 1; /* unreachable */
+}
diff --git a/dmenuo/dmenu.o b/dmenuo/dmenu.o
new file mode 100644
index 0000000..9394613
--- /dev/null
+++ b/dmenuo/dmenu.o
Binary files differ
diff --git a/dmenuo/dmenu_path b/dmenuo/dmenu_path
new file mode 100755
index 0000000..3a7cda7
--- /dev/null
+++ b/dmenuo/dmenu_path
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}"
+cache="$cachedir/dmenu_run"
+
+[ ! -e "$cachedir" ] && mkdir -p "$cachedir"
+
+IFS=:
+if stest -dqr -n "$cache" $PATH; then
+ stest -flx $PATH | sort -u | tee "$cache"
+else
+ cat "$cache"
+fi
diff --git a/dmenuo/dmenu_run b/dmenuo/dmenu_run
new file mode 100755
index 0000000..834ede5
--- /dev/null
+++ b/dmenuo/dmenu_run
@@ -0,0 +1,2 @@
+#!/bin/sh
+dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &
diff --git a/dmenuo/drw.c b/dmenuo/drw.c
new file mode 100755
index 0000000..a58a2b4
--- /dev/null
+++ b/dmenuo/drw.c
@@ -0,0 +1,450 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+#include "util.h"
+
+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+
+static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
+static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+static long
+utf8decodebyte(const char c, size_t *i)
+{
+ for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
+ if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
+ return (unsigned char)c & ~utfmask[*i];
+ return 0;
+}
+
+static size_t
+utf8validate(long *u, size_t i)
+{
+ if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
+ *u = UTF_INVALID;
+ for (i = 1; *u > utfmax[i]; ++i)
+ ;
+ return i;
+}
+
+static size_t
+utf8decode(const char *c, long *u, size_t clen)
+{
+ size_t i, j, len, type;
+ long udecoded;
+
+ *u = UTF_INVALID;
+ if (!clen)
+ return 0;
+ udecoded = utf8decodebyte(c[0], &len);
+ if (!BETWEEN(len, 1, UTF_SIZ))
+ return 1;
+ for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
+ if (type)
+ return j;
+ }
+ if (j < len)
+ return 0;
+ *u = udecoded;
+ utf8validate(u, len);
+
+ return len;
+}
+
+Drw *
+drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
+{
+ Drw *drw = ecalloc(1, sizeof(Drw));
+
+ drw->dpy = dpy;
+ drw->screen = screen;
+ drw->root = root;
+ drw->w = w;
+ drw->h = h;
+ drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
+ drw->gc = XCreateGC(dpy, root, 0, NULL);
+ XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
+
+ return drw;
+}
+
+void
+drw_resize(Drw *drw, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ drw->w = w;
+ drw->h = h;
+ if (drw->drawable)
+ XFreePixmap(drw->dpy, drw->drawable);
+ drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
+}
+
+void
+drw_free(Drw *drw)
+{
+ XFreePixmap(drw->dpy, drw->drawable);
+ XFreeGC(drw->dpy, drw->gc);
+ drw_fontset_free(drw->fonts);
+ free(drw);
+}
+
+/* This function is an implementation detail. Library users should use
+ * drw_fontset_create instead.
+ */
+static Fnt *
+xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
+{
+ Fnt *font;
+ XftFont *xfont = NULL;
+ FcPattern *pattern = NULL;
+
+ if (fontname) {
+ /* Using the pattern found at font->xfont->pattern does not yield the
+ * same substitution results as using the pattern returned by
+ * FcNameParse; using the latter results in the desired fallback
+ * behaviour whereas the former just results in missing-character
+ * rectangles being drawn, at least with some fonts. */
+ if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
+ fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
+ return NULL;
+ }
+ if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
+ fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
+ XftFontClose(drw->dpy, xfont);
+ return NULL;
+ }
+ } else if (fontpattern) {
+ if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
+ fprintf(stderr, "error, cannot load font from pattern.\n");
+ return NULL;
+ }
+ } else {
+ die("no font specified.");
+ }
+
+ font = ecalloc(1, sizeof(Fnt));
+ font->xfont = xfont;
+ font->pattern = pattern;
+ font->h = xfont->ascent + xfont->descent;
+ font->dpy = drw->dpy;
+
+ return font;
+}
+
+static void
+xfont_free(Fnt *font)
+{
+ if (!font)
+ return;
+ if (font->pattern)
+ FcPatternDestroy(font->pattern);
+ XftFontClose(font->dpy, font->xfont);
+ free(font);
+}
+
+Fnt*
+drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)
+{
+ Fnt *cur, *ret = NULL;
+ size_t i;
+
+ if (!drw || !fonts)
+ return NULL;
+
+ for (i = 1; i <= fontcount; i++) {
+ if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
+ cur->next = ret;
+ ret = cur;
+ }
+ }
+ return (drw->fonts = ret);
+}
+
+void
+drw_fontset_free(Fnt *font)
+{
+ if (font) {
+ drw_fontset_free(font->next);
+ xfont_free(font);
+ }
+}
+
+void
+drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
+{
+ if (!drw || !dest || !clrname)
+ return;
+
+ if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen),
+ clrname, dest))
+ die("error, cannot allocate color '%s'", clrname);
+}
+
+/* Wrapper to create color schemes. The caller has to call free(3) on the
+ * returned color scheme when done using it. */
+Clr *
+drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
+{
+ size_t i;
+ Clr *ret;
+
+ /* need at least two colors for a scheme */
+ if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
+ return NULL;
+
+ for (i = 0; i < clrcount; i++)
+ drw_clr_create(drw, &ret[i], clrnames[i]);
+ return ret;
+}
+
+void
+drw_setfontset(Drw *drw, Fnt *set)
+{
+ if (drw)
+ drw->fonts = set;
+}
+
+void
+drw_setscheme(Drw *drw, Clr *scm)
+{
+ if (drw)
+ drw->scheme = scm;
+}
+
+void
+drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
+{
+ if (!drw || !drw->scheme)
+ return;
+ XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
+ if (filled)
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ else
+ XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
+}
+
+int
+drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
+{
+ int i, ty, ellipsis_x = 0;
+ unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len;
+ XftDraw *d = NULL;
+ Fnt *usedfont, *curfont, *nextfont;
+ int utf8strlen, utf8charlen, render = x || y || w || h;
+ long utf8codepoint = 0;
+ const char *utf8str;
+ FcCharSet *fccharset;
+ FcPattern *fcpattern;
+ FcPattern *match;
+ XftResult result;
+ int charexists = 0, overflow = 0;
+ /* keep track of a couple codepoints for which we have no match. */
+ enum { nomatches_len = 64 };
+ static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;
+ static unsigned int ellipsis_width = 0;
+
+ if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
+ return 0;
+
+ if (!render) {
+ w = invert ? invert : ~invert;
+ } else {
+ XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ d = XftDrawCreate(drw->dpy, drw->drawable,
+ DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen));
+ x += lpad;
+ w -= lpad;
+ }
+
+ usedfont = drw->fonts;
+ if (!ellipsis_width && render)
+ ellipsis_width = drw_fontset_getwidth(drw, "...");
+ while (1) {
+ ew = ellipsis_len = utf8strlen = 0;
+ utf8str = text;
+ nextfont = NULL;
+ while (*text) {
+ utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
+ for (curfont = drw->fonts; curfont; curfont = curfont->next) {
+ charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
+ if (charexists) {
+ drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL);
+ if (ew + ellipsis_width <= w) {
+ /* keep track where the ellipsis still fits */
+ ellipsis_x = x + ew;
+ ellipsis_w = w - ew;
+ ellipsis_len = utf8strlen;
+ }
+
+ if (ew + tmpw > w) {
+ overflow = 1;
+ /* called from drw_fontset_getwidth_clamp():
+ * it wants the width AFTER the overflow
+ */
+ if (!render)
+ x += tmpw;
+ else
+ utf8strlen = ellipsis_len;
+ } else if (curfont == usedfont) {
+ utf8strlen += utf8charlen;
+ text += utf8charlen;
+ ew += tmpw;
+ } else {
+ nextfont = curfont;
+ }
+ break;
+ }
+ }
+
+ if (overflow || !charexists || nextfont)
+ break;
+ else
+ charexists = 0;
+ }
+
+ if (utf8strlen) {
+ if (render) {
+ ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
+ XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
+ usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen);
+ }
+ x += ew;
+ w -= ew;
+ }
+ if (render && overflow)
+ drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
+
+ if (!*text || overflow) {
+ break;
+ } else if (nextfont) {
+ charexists = 0;
+ usedfont = nextfont;
+ } else {
+ /* Regardless of whether or not a fallback font is found, the
+ * character must be drawn. */
+ charexists = 1;
+
+ for (i = 0; i < nomatches_len; ++i) {
+ /* avoid calling XftFontMatch if we know we won't find a match */
+ if (utf8codepoint == nomatches.codepoint[i])
+ goto no_match;
+ }
+
+ fccharset = FcCharSetCreate();
+ FcCharSetAddChar(fccharset, utf8codepoint);
+
+ if (!drw->fonts->pattern) {
+ /* Refer to the comment in xfont_create for more information. */
+ die("the first font in the cache must be loaded from a font string.");
+ }
+
+ fcpattern = FcPatternDuplicate(drw->fonts->pattern);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
+
+ FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+ match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
+
+ FcCharSetDestroy(fccharset);
+ FcPatternDestroy(fcpattern);
+
+ if (match) {
+ usedfont = xfont_create(drw, NULL, match);
+ if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
+ for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
+ ; /* NOP */
+ curfont->next = usedfont;
+ } else {
+ xfont_free(usedfont);
+ nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint;
+no_match:
+ usedfont = drw->fonts;
+ }
+ }
+ }
+ }
+ if (d)
+ XftDrawDestroy(d);
+
+ return x + (render ? w : 0);
+}
+
+void
+drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
+ XSync(drw->dpy, False);
+}
+
+unsigned int
+drw_fontset_getwidth(Drw *drw, const char *text)
+{
+ if (!drw || !drw->fonts || !text)
+ return 0;
+ return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
+}
+
+unsigned int
+drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n)
+{
+ unsigned int tmp = 0;
+ if (drw && drw->fonts && text && n)
+ tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n);
+ return MIN(n, tmp);
+}
+
+void
+drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
+{
+ XGlyphInfo ext;
+
+ if (!font || !text)
+ return;
+
+ XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
+ if (w)
+ *w = ext.xOff;
+ if (h)
+ *h = font->h;
+}
+
+Cur *
+drw_cur_create(Drw *drw, int shape)
+{
+ Cur *cur;
+
+ if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
+ return NULL;
+
+ cur->cursor = XCreateFontCursor(drw->dpy, shape);
+
+ return cur;
+}
+
+void
+drw_cur_free(Drw *drw, Cur *cursor)
+{
+ if (!cursor)
+ return;
+
+ XFreeCursor(drw->dpy, cursor->cursor);
+ free(cursor);
+}
diff --git a/dmenuo/drw.h b/dmenuo/drw.h
new file mode 100755
index 0000000..fd7631b
--- /dev/null
+++ b/dmenuo/drw.h
@@ -0,0 +1,58 @@
+/* See LICENSE file for copyright and license details. */
+
+typedef struct {
+ Cursor cursor;
+} Cur;
+
+typedef struct Fnt {
+ Display *dpy;
+ unsigned int h;
+ XftFont *xfont;
+ FcPattern *pattern;
+ struct Fnt *next;
+} Fnt;
+
+enum { ColFg, ColBg }; /* Clr scheme index */
+typedef XftColor Clr;
+
+typedef struct {
+ unsigned int w, h;
+ Display *dpy;
+ int screen;
+ Window root;
+ Drawable drawable;
+ GC gc;
+ Clr *scheme;
+ Fnt *fonts;
+} Drw;
+
+/* Drawable abstraction */
+Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h);
+void drw_resize(Drw *drw, unsigned int w, unsigned int h);
+void drw_free(Drw *drw);
+
+/* Fnt abstraction */
+Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount);
+void drw_fontset_free(Fnt* set);
+unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
+unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n);
+void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
+
+/* Colorscheme abstraction */
+void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
+Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
+
+/* Cursor abstraction */
+Cur *drw_cur_create(Drw *drw, int shape);
+void drw_cur_free(Drw *drw, Cur *cursor);
+
+/* Drawing context manipulation */
+void drw_setfontset(Drw *drw, Fnt *set);
+void drw_setscheme(Drw *drw, Clr *scm);
+
+/* Drawing functions */
+void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
+int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
+
+/* Map functions */
+void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
diff --git a/dmenuo/drw.o b/dmenuo/drw.o
new file mode 100644
index 0000000..86603d2
--- /dev/null
+++ b/dmenuo/drw.o
Binary files differ
diff --git a/dmenuo/stest b/dmenuo/stest
new file mode 100755
index 0000000..fd35c32
--- /dev/null
+++ b/dmenuo/stest
Binary files differ
diff --git a/dmenuo/stest.1 b/dmenuo/stest.1
new file mode 100755
index 0000000..2667d8a
--- /dev/null
+++ b/dmenuo/stest.1
@@ -0,0 +1,90 @@
+.TH STEST 1 dmenu\-VERSION
+.SH NAME
+stest \- filter a list of files by properties
+.SH SYNOPSIS
+.B stest
+.RB [ -abcdefghlpqrsuwx ]
+.RB [ -n
+.IR file ]
+.RB [ -o
+.IR file ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B stest
+takes a list of files and filters by the files' properties, analogous to
+.IR test (1).
+Files which pass all tests are printed to stdout. If no files are given, stest
+reads files from stdin.
+.SH OPTIONS
+.TP
+.B \-a
+Test hidden files.
+.TP
+.B \-b
+Test that files are block specials.
+.TP
+.B \-c
+Test that files are character specials.
+.TP
+.B \-d
+Test that files are directories.
+.TP
+.B \-e
+Test that files exist.
+.TP
+.B \-f
+Test that files are regular files.
+.TP
+.B \-g
+Test that files have their set-group-ID flag set.
+.TP
+.B \-h
+Test that files are symbolic links.
+.TP
+.B \-l
+Test the contents of a directory given as an argument.
+.TP
+.BI \-n " file"
+Test that files are newer than
+.IR file .
+.TP
+.BI \-o " file"
+Test that files are older than
+.IR file .
+.TP
+.B \-p
+Test that files are named pipes.
+.TP
+.B \-q
+No files are printed, only the exit status is returned.
+.TP
+.B \-r
+Test that files are readable.
+.TP
+.B \-s
+Test that files are not empty.
+.TP
+.B \-u
+Test that files have their set-user-ID flag set.
+.TP
+.B \-v
+Invert the sense of tests, only failing files pass.
+.TP
+.B \-w
+Test that files are writable.
+.TP
+.B \-x
+Test that files are executable.
+.SH EXIT STATUS
+.TP
+.B 0
+At least one file passed all tests.
+.TP
+.B 1
+No files passed all tests.
+.TP
+.B 2
+An error occurred.
+.SH SEE ALSO
+.IR dmenu (1),
+.IR test (1)
diff --git a/dmenuo/stest.c b/dmenuo/stest.c
new file mode 100755
index 0000000..e27d3a5
--- /dev/null
+++ b/dmenuo/stest.c
@@ -0,0 +1,109 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arg.h"
+char *argv0;
+
+#define FLAG(x) (flag[(x)-'a'])
+
+static void test(const char *, const char *);
+static void usage(void);
+
+static int match = 0;
+static int flag[26];
+static struct stat old, new;
+
+static void
+test(const char *path, const char *name)
+{
+ struct stat st, ln;
+
+ if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */
+ && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */
+ && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */
+ && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */
+ && (!FLAG('e') || access(path, F_OK) == 0) /* exists */
+ && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */
+ && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */
+ && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */
+ && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */
+ && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */
+ && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */
+ && (!FLAG('r') || access(path, R_OK) == 0) /* readable */
+ && (!FLAG('s') || st.st_size > 0) /* not empty */
+ && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */
+ && (!FLAG('w') || access(path, W_OK) == 0) /* writable */
+ && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */
+ if (FLAG('q'))
+ exit(0);
+ match = 1;
+ puts(name);
+ }
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] "
+ "[-n file] [-o file] [file...]\n", argv0);
+ exit(2); /* like test(1) return > 1 on error */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct dirent *d;
+ char path[PATH_MAX], *line = NULL, *file;
+ size_t linesiz = 0;
+ ssize_t n;
+ DIR *dir;
+ int r;
+
+ ARGBEGIN {
+ case 'n': /* newer than file */
+ case 'o': /* older than file */
+ file = EARGF(usage());
+ if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old))))
+ perror(file);
+ break;
+ default:
+ /* miscellaneous operators */
+ if (strchr("abcdefghlpqrsuvwx", ARGC()))
+ FLAG(ARGC()) = 1;
+ else
+ usage(); /* unknown flag */
+ } ARGEND;
+
+ if (!argc) {
+ /* read list from stdin */
+ while ((n = getline(&line, &linesiz, stdin)) > 0) {
+ if (line[n - 1] == '\n')
+ line[n - 1] = '\0';
+ test(line, line);
+ }
+ free(line);
+ } else {
+ for (; argc; argc--, argv++) {
+ if (FLAG('l') && (dir = opendir(*argv))) {
+ /* test directory contents */
+ while ((d = readdir(dir))) {
+ r = snprintf(path, sizeof path, "%s/%s",
+ *argv, d->d_name);
+ if (r >= 0 && (size_t)r < sizeof path)
+ test(path, d->d_name);
+ }
+ closedir(dir);
+ } else {
+ test(*argv, *argv);
+ }
+ }
+ }
+ return match ? 0 : 1;
+}
diff --git a/dmenuo/stest.o b/dmenuo/stest.o
new file mode 100644
index 0000000..2f98e41
--- /dev/null
+++ b/dmenuo/stest.o
Binary files differ
diff --git a/dmenuo/util.c b/dmenuo/util.c
new file mode 100755
index 0000000..96b82c9
--- /dev/null
+++ b/dmenuo/util.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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/dmenuo/util.h b/dmenuo/util.h
new file mode 100755
index 0000000..f633b51
--- /dev/null
+++ b/dmenuo/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/dmenuo/util.o b/dmenuo/util.o
new file mode 100644
index 0000000..6ab501f
--- /dev/null
+++ b/dmenuo/util.o
Binary files differ
diff --git a/dwmo/LICENSE b/dwmo/LICENSE
new file mode 100755
index 0000000..995172f
--- /dev/null
+++ b/dwmo/LICENSE
@@ -0,0 +1,38 @@
+MIT/X Consortium License
+
+© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
+© 2006-2009 Jukka Salmi <jukka at salmi dot ch>
+© 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com>
+© 2007-2011 Peter Hartlich <sgkkr at hartlich dot com>
+© 2007-2009 Szabolcs Nagy <nszabolcs at gmail dot com>
+© 2007-2009 Christof Musik <christof at sendfax dot de>
+© 2007-2009 Premysl Hruby <dfenze at gmail dot com>
+© 2007-2008 Enno Gottox Boland <gottox at s01 dot de>
+© 2008 Martin Hurton <martin dot hurton at gmail dot com>
+© 2008 Neale Pickett <neale dot woozle dot org>
+© 2009 Mate Nagy <mnagy at port70 dot net>
+© 2010-2016 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2010-2012 Connor Lane Smith <cls@lubutu.com>
+© 2011 Christoph Lohmann <20h@r-36.net>
+© 2015-2016 Quentin Rameau <quinq@fifth.space>
+© 2015-2016 Eric Pruitt <eric.pruitt@gmail.com>
+© 2016-2017 Markus Teich <markus.teich@stusta.mhn.de>
+© 2020-2022 Chris Down <chris@chrisdown.name>
+
+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/dwmo/Makefile b/dwmo/Makefile
new file mode 100755
index 0000000..77bcbc0
--- /dev/null
+++ b/dwmo/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/dwmo/README b/dwmo/README
new file mode 100755
index 0000000..07d4a22
--- /dev/null
+++ b/dwmo/README
@@ -0,0 +1,49 @@
+dwmo - dynamic window manager for openbsd
+=========================================
+
+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/dwmo/config.def.h b/dwmo/config.def.h
new file mode 100755
index 0000000..edb45bf
--- /dev/null
+++ b/dwmo/config.def.h
@@ -0,0 +1,142 @@
+/* See LICENSE file for copyright and license details. */
+
+/* includes */
+#include <X11/XF86keysym.h>
+
+/* appearance */
+static const unsigned int borderpx = 1; /* border pixel of windows */
+static const unsigned int snap = 32; /* snap pixel */
+static const int showbar = 1; /* 0 means no bar */
+static const int topbar = 1; /* 0 means bottom bar */
+static const char *fonts[] = { "JetBrains Mono:size=8" };
+static const char dmenufont[] = "JetBrains Mono:size=8";
+static const char col_gray1[] = "#222222";
+static const char col_gray2[] = "#444444";
+static const char col_gray3[] = "#bbbbbb";
+static const char col_gray4[] = "#eeeeee";
+static const char col_cyan[] = "#005577";
+static const char col_cust[] = "#34ace0";
+static const char *colors[][3] = {
+ /* fg bg border */
+ [SchemeNorm] = { col_gray3, col_gray1, col_gray2 },
+ [SchemeSel] = { col_gray4, col_cyan, col_cyan },
+};
+
+/* tagging */
+static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+
+static const Rule rules[] = {
+ /* xprop(1):
+ * WM_CLASS(STRING) = instance, class
+ * WM_NAME(STRING) = title
+ */
+ /* class instance title tags mask iscentered isfloating monitor */
+ { "Gimp", NULL, NULL, 0, 0, 1, -1 },
+ { "firefox", NULL, NULL, 0, 1, 0, -1 },
+ { "qutebrowser", NULL, NULL, 0, 1, 0, -1 },
+ { "surf", NULL, NULL, 0, 1, 0, -1 },
+ { "st", NULL, NULL, 0, 1, 0, -1 },
+ { "feh", NULL, NULL, 0, 1, 0, -1 },
+};
+
+/* layout(s) */
+static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */
+static const int nmaster = 1; /* number of clients in master area */
+static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */
+static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */
+
+static const Layout layouts[] = {
+ /* symbol arrange function */
+ { "[]=", tile }, /* first entry is default */
+ { "><>", NULL }, /* no layout function means floating behavior */
+ { "[M]", monocle },
+};
+
+/* key definitions */
+#define MODKEY Mod1Mask
+#define TAGKEYS(KEY,TAG) \
+ { MODKEY, KEY, view, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \
+ { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} },
+
+/* helper for spawning shell commands in the pre dwm-5.0 fashion */
+#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }
+
+/* commands */
+static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */
+static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL };
+static const char *termcmd[] = { "st", NULL };
+static const char *webcmd[] = { "qutebrowser", NULL };
+static const char *lockcmd[] = { "slock", NULL };
+static const char *brupcmd[] = { "doas", "light", "-A", "10", NULL };
+static const char *brdowncmd[] = { "doas", "light", "-U", "10", NULL };
+static const char *upvol[] = { "amixer", "-q", "set", "Master", "5%+", "unmute", NULL };
+static const char *downvol[] = { "amixer", "-q", "set", "Master", "5%-", "unmute", NULL };
+static const char *mutevol[] = { "amixer", "-q", "set", "Master", "toggle", NULL };
+
+
+static const Key keys[] = {
+ /* modifier key function argument */
+ { MODKEY, XK_p, spawn, {.v = dmenucmd } },
+ { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } },
+ { MODKEY|ShiftMask, XK_o, spawn, {.v = webcmd } },
+ { MODKEY|ShiftMask, XK_l, spawn, {.v = lockcmd } },
+ { MODKEY|ShiftMask, XK_s, spawn, SHCMD("slock & doas zzz") },
+ { MODKEY, XK_b, togglebar, {0} },
+ { MODKEY, XK_j, focusstack, {.i = +1 } },
+ { MODKEY, XK_k, focusstack, {.i = -1 } },
+ { MODKEY, XK_i, incnmaster, {.i = +1 } },
+ { MODKEY, XK_d, incnmaster, {.i = -1 } },
+ { MODKEY, XK_h, setmfact, {.f = -0.05} },
+ { MODKEY, XK_l, setmfact, {.f = +0.05} },
+ { MODKEY, XK_Return, zoom, {0} },
+ { MODKEY, XK_Tab, view, {0} },
+ { MODKEY|ShiftMask, XK_c, killclient, {0} },
+ { MODKEY, XK_t, setlayout, {.v = &layouts[0]} },
+ { MODKEY, XK_f, setlayout, {.v = &layouts[1]} },
+ { MODKEY, XK_m, setlayout, {.v = &layouts[2]} },
+ { MODKEY, XK_space, setlayout, {0} },
+ { MODKEY|ShiftMask, XK_space, togglefloating, {0} },
+ { MODKEY, XK_0, view, {.ui = ~0 } },
+ { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } },
+ { MODKEY, XK_comma, focusmon, {.i = -1 } },
+ { MODKEY, XK_period, focusmon, {.i = +1 } },
+ { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } },
+ { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } },
+ TAGKEYS( XK_1, 0)
+ TAGKEYS( XK_2, 1)
+ TAGKEYS( XK_3, 2)
+ TAGKEYS( XK_4, 3)
+ TAGKEYS( XK_5, 4)
+ TAGKEYS( XK_6, 5)
+ TAGKEYS( XK_7, 6)
+ TAGKEYS( XK_8, 7)
+ TAGKEYS( XK_9, 8)
+ { MODKEY|ShiftMask, XK_q, quit, {0} },
+ { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} },
+ { 0, XK_Print, spawn, SHCMD("scrot -a $(slop -f '%x,%y,%w,%h')") },
+ { 0, XF86XK_AudioLowerVolume, spawn, {.v = downvol}},
+ { 0, XF86XK_AudioRaiseVolume, spawn, {.v = upvol}},
+ { 0, XF86XK_AudioMute, spawn, {.v = mutevol}},
+ { 0, XK_F6, spawn, {.v = brupcmd} },
+ { 0, XK_F5, spawn, {.v = brdowncmd} },
+};
+
+/* 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/dwmo/config.h b/dwmo/config.h
new file mode 100755
index 0000000..f8acaf8
--- /dev/null
+++ b/dwmo/config.h
@@ -0,0 +1,142 @@
+/* See LICENSE file for copyright and license details. */
+
+/* includes */
+#include <X11/XF86keysym.h>
+
+/* appearance */
+static const unsigned int borderpx = 1; /* border pixel of windows */
+static const unsigned int snap = 32; /* snap pixel */
+static const int showbar = 1; /* 0 means no bar */
+static const int topbar = 1; /* 0 means bottom bar */
+static const char *fonts[] = { "Terminus:size=8" };
+static const char dmenufont[] = "Terminus:size=8";
+static const char col_gray1[] = "#222222";
+static const char col_gray2[] = "#444444";
+static const char col_gray3[] = "#bbbbbb";
+static const char col_gray4[] = "#eeeeee";
+static const char col_cyan[] = "#005577";
+static const char col_cust[] = "#34ace0";
+static const char *colors[][3] = {
+ /* fg bg border */
+ [SchemeNorm] = { col_gray3, col_gray1, col_gray2 },
+ [SchemeSel] = { col_gray4, col_cyan, col_cyan },
+};
+
+/* tagging */
+static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+
+static const Rule rules[] = {
+ /* xprop(1):
+ * WM_CLASS(STRING) = instance, class
+ * WM_NAME(STRING) = title
+ */
+ /* class instance title tags mask iscentered isfloating monitor */
+ { "Gimp", NULL, NULL, 0, 0, 1, -1 },
+ { "firefox", NULL, NULL, 0, 1, 0, -1 },
+ { "qutebrowser", NULL, NULL, 0, 1, 0, -1 },
+ { "surf", NULL, NULL, 0, 1, 0, -1 },
+ { "st", NULL, NULL, 0, 1, 0, -1 },
+ { "feh", NULL, NULL, 0, 1, 0, -1 },
+};
+
+/* layout(s) */
+static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */
+static const int nmaster = 1; /* number of clients in master area */
+static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */
+static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */
+
+static const Layout layouts[] = {
+ /* symbol arrange function */
+ { "[]=", tile }, /* first entry is default */
+ { "><>", NULL }, /* no layout function means floating behavior */
+ { "[M]", monocle },
+};
+
+/* key definitions */
+#define MODKEY Mod1Mask
+#define TAGKEYS(KEY,TAG) \
+ { MODKEY, KEY, view, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \
+ { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} },
+
+/* helper for spawning shell commands in the pre dwm-5.0 fashion */
+#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }
+
+/* commands */
+static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */
+static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL };
+static const char *termcmd[] = { "st", NULL };
+static const char *webcmd[] = { "firefox", NULL };
+static const char *lockcmd[] = { "slock", NULL };
+static const char *brupcmd[] = { "doas", "light", "-A", "10", NULL };
+static const char *brdowncmd[] = { "doas", "light", "-U", "10", NULL };
+static const char *upvol[] = { "amixer", "-q", "set", "Master", "5%+", "unmute", NULL };
+static const char *downvol[] = { "amixer", "-q", "set", "Master", "5%-", "unmute", NULL };
+static const char *mutevol[] = { "amixer", "-q", "set", "Master", "toggle", NULL };
+
+
+static const Key keys[] = {
+ /* modifier key function argument */
+ { MODKEY, XK_p, spawn, {.v = dmenucmd } },
+ { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } },
+ { MODKEY|ShiftMask, XK_o, spawn, {.v = webcmd } },
+ { MODKEY|ShiftMask, XK_l, spawn, {.v = lockcmd } },
+ { MODKEY|ShiftMask, XK_s, spawn, SHCMD("slock & doas zzz") },
+ { MODKEY, XK_b, togglebar, {0} },
+ { MODKEY, XK_j, focusstack, {.i = +1 } },
+ { MODKEY, XK_k, focusstack, {.i = -1 } },
+ { MODKEY, XK_i, incnmaster, {.i = +1 } },
+ { MODKEY, XK_d, incnmaster, {.i = -1 } },
+ { MODKEY, XK_h, setmfact, {.f = -0.05} },
+ { MODKEY, XK_l, setmfact, {.f = +0.05} },
+ { MODKEY, XK_Return, zoom, {0} },
+ { MODKEY, XK_Tab, view, {0} },
+ { MODKEY|ShiftMask, XK_c, killclient, {0} },
+ { MODKEY, XK_t, setlayout, {.v = &layouts[0]} },
+ { MODKEY, XK_f, setlayout, {.v = &layouts[1]} },
+ { MODKEY, XK_m, setlayout, {.v = &layouts[2]} },
+ { MODKEY, XK_space, setlayout, {0} },
+ { MODKEY|ShiftMask, XK_space, togglefloating, {0} },
+ { MODKEY, XK_0, view, {.ui = ~0 } },
+ { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } },
+ { MODKEY, XK_comma, focusmon, {.i = -1 } },
+ { MODKEY, XK_period, focusmon, {.i = +1 } },
+ { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } },
+ { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } },
+ TAGKEYS( XK_1, 0)
+ TAGKEYS( XK_2, 1)
+ TAGKEYS( XK_3, 2)
+ TAGKEYS( XK_4, 3)
+ TAGKEYS( XK_5, 4)
+ TAGKEYS( XK_6, 5)
+ TAGKEYS( XK_7, 6)
+ TAGKEYS( XK_8, 7)
+ TAGKEYS( XK_9, 8)
+ { MODKEY|ShiftMask, XK_q, quit, {0} },
+ { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} },
+ { 0, XK_Print, spawn, SHCMD("scrot -a $(slop -f '%x,%y,%w,%h')") },
+ { 0, XF86XK_AudioLowerVolume, spawn, {.v = downvol}},
+ { 0, XF86XK_AudioRaiseVolume, spawn, {.v = upvol}},
+ { 0, XF86XK_AudioMute, spawn, {.v = mutevol}},
+ { 0, XK_F6, spawn, {.v = brupcmd} },
+ { 0, XK_F5, spawn, {.v = brdowncmd} },
+};
+
+/* 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/dwmo/config.mk b/dwmo/config.mk
new file mode 100755
index 0000000..37826f0
--- /dev/null
+++ b/dwmo/config.mk
@@ -0,0 +1,39 @@
+# dwm version
+VERSION = 6.4
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+
+# Xinerama, comment if you don't want it
+XINERAMALIBS = -lXinerama
+XINERAMAFLAGS = -DXINERAMA
+
+# freetype
+FREETYPELIBS = -lfontconfig -lXft
+FREETYPEINC = /usr/include/freetype2
+# OpenBSD (uncomment)
+FREETYPEINC = ${X11INC}/freetype2
+MANPREFIX = ${PREFIX}/man
+
+# includes and libs
+INCS = -I${X11INC} -I${FREETYPEINC}
+LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS}
+
+# flags
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
+#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
+CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS}
+LDFLAGS = ${LIBS}
+
+# Solaris
+#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\"
+#LDFLAGS = ${LIBS}
+
+# compiler and linker
+CC = cc
diff --git a/dwmo/drw.c b/dwmo/drw.c
new file mode 100755
index 0000000..a58a2b4
--- /dev/null
+++ b/dwmo/drw.c
@@ -0,0 +1,450 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+#include "util.h"
+
+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+
+static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
+static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+static long
+utf8decodebyte(const char c, size_t *i)
+{
+ for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
+ if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
+ return (unsigned char)c & ~utfmask[*i];
+ return 0;
+}
+
+static size_t
+utf8validate(long *u, size_t i)
+{
+ if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
+ *u = UTF_INVALID;
+ for (i = 1; *u > utfmax[i]; ++i)
+ ;
+ return i;
+}
+
+static size_t
+utf8decode(const char *c, long *u, size_t clen)
+{
+ size_t i, j, len, type;
+ long udecoded;
+
+ *u = UTF_INVALID;
+ if (!clen)
+ return 0;
+ udecoded = utf8decodebyte(c[0], &len);
+ if (!BETWEEN(len, 1, UTF_SIZ))
+ return 1;
+ for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
+ if (type)
+ return j;
+ }
+ if (j < len)
+ return 0;
+ *u = udecoded;
+ utf8validate(u, len);
+
+ return len;
+}
+
+Drw *
+drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
+{
+ Drw *drw = ecalloc(1, sizeof(Drw));
+
+ drw->dpy = dpy;
+ drw->screen = screen;
+ drw->root = root;
+ drw->w = w;
+ drw->h = h;
+ drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
+ drw->gc = XCreateGC(dpy, root, 0, NULL);
+ XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
+
+ return drw;
+}
+
+void
+drw_resize(Drw *drw, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ drw->w = w;
+ drw->h = h;
+ if (drw->drawable)
+ XFreePixmap(drw->dpy, drw->drawable);
+ drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
+}
+
+void
+drw_free(Drw *drw)
+{
+ XFreePixmap(drw->dpy, drw->drawable);
+ XFreeGC(drw->dpy, drw->gc);
+ drw_fontset_free(drw->fonts);
+ free(drw);
+}
+
+/* This function is an implementation detail. Library users should use
+ * drw_fontset_create instead.
+ */
+static Fnt *
+xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
+{
+ Fnt *font;
+ XftFont *xfont = NULL;
+ FcPattern *pattern = NULL;
+
+ if (fontname) {
+ /* Using the pattern found at font->xfont->pattern does not yield the
+ * same substitution results as using the pattern returned by
+ * FcNameParse; using the latter results in the desired fallback
+ * behaviour whereas the former just results in missing-character
+ * rectangles being drawn, at least with some fonts. */
+ if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
+ fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
+ return NULL;
+ }
+ if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
+ fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
+ XftFontClose(drw->dpy, xfont);
+ return NULL;
+ }
+ } else if (fontpattern) {
+ if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
+ fprintf(stderr, "error, cannot load font from pattern.\n");
+ return NULL;
+ }
+ } else {
+ die("no font specified.");
+ }
+
+ font = ecalloc(1, sizeof(Fnt));
+ font->xfont = xfont;
+ font->pattern = pattern;
+ font->h = xfont->ascent + xfont->descent;
+ font->dpy = drw->dpy;
+
+ return font;
+}
+
+static void
+xfont_free(Fnt *font)
+{
+ if (!font)
+ return;
+ if (font->pattern)
+ FcPatternDestroy(font->pattern);
+ XftFontClose(font->dpy, font->xfont);
+ free(font);
+}
+
+Fnt*
+drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)
+{
+ Fnt *cur, *ret = NULL;
+ size_t i;
+
+ if (!drw || !fonts)
+ return NULL;
+
+ for (i = 1; i <= fontcount; i++) {
+ if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
+ cur->next = ret;
+ ret = cur;
+ }
+ }
+ return (drw->fonts = ret);
+}
+
+void
+drw_fontset_free(Fnt *font)
+{
+ if (font) {
+ drw_fontset_free(font->next);
+ xfont_free(font);
+ }
+}
+
+void
+drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
+{
+ if (!drw || !dest || !clrname)
+ return;
+
+ if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen),
+ clrname, dest))
+ die("error, cannot allocate color '%s'", clrname);
+}
+
+/* Wrapper to create color schemes. The caller has to call free(3) on the
+ * returned color scheme when done using it. */
+Clr *
+drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
+{
+ size_t i;
+ Clr *ret;
+
+ /* need at least two colors for a scheme */
+ if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
+ return NULL;
+
+ for (i = 0; i < clrcount; i++)
+ drw_clr_create(drw, &ret[i], clrnames[i]);
+ return ret;
+}
+
+void
+drw_setfontset(Drw *drw, Fnt *set)
+{
+ if (drw)
+ drw->fonts = set;
+}
+
+void
+drw_setscheme(Drw *drw, Clr *scm)
+{
+ if (drw)
+ drw->scheme = scm;
+}
+
+void
+drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
+{
+ if (!drw || !drw->scheme)
+ return;
+ XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
+ if (filled)
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ else
+ XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
+}
+
+int
+drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
+{
+ int i, ty, ellipsis_x = 0;
+ unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len;
+ XftDraw *d = NULL;
+ Fnt *usedfont, *curfont, *nextfont;
+ int utf8strlen, utf8charlen, render = x || y || w || h;
+ long utf8codepoint = 0;
+ const char *utf8str;
+ FcCharSet *fccharset;
+ FcPattern *fcpattern;
+ FcPattern *match;
+ XftResult result;
+ int charexists = 0, overflow = 0;
+ /* keep track of a couple codepoints for which we have no match. */
+ enum { nomatches_len = 64 };
+ static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;
+ static unsigned int ellipsis_width = 0;
+
+ if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
+ return 0;
+
+ if (!render) {
+ w = invert ? invert : ~invert;
+ } else {
+ XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ d = XftDrawCreate(drw->dpy, drw->drawable,
+ DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen));
+ x += lpad;
+ w -= lpad;
+ }
+
+ usedfont = drw->fonts;
+ if (!ellipsis_width && render)
+ ellipsis_width = drw_fontset_getwidth(drw, "...");
+ while (1) {
+ ew = ellipsis_len = utf8strlen = 0;
+ utf8str = text;
+ nextfont = NULL;
+ while (*text) {
+ utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
+ for (curfont = drw->fonts; curfont; curfont = curfont->next) {
+ charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
+ if (charexists) {
+ drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL);
+ if (ew + ellipsis_width <= w) {
+ /* keep track where the ellipsis still fits */
+ ellipsis_x = x + ew;
+ ellipsis_w = w - ew;
+ ellipsis_len = utf8strlen;
+ }
+
+ if (ew + tmpw > w) {
+ overflow = 1;
+ /* called from drw_fontset_getwidth_clamp():
+ * it wants the width AFTER the overflow
+ */
+ if (!render)
+ x += tmpw;
+ else
+ utf8strlen = ellipsis_len;
+ } else if (curfont == usedfont) {
+ utf8strlen += utf8charlen;
+ text += utf8charlen;
+ ew += tmpw;
+ } else {
+ nextfont = curfont;
+ }
+ break;
+ }
+ }
+
+ if (overflow || !charexists || nextfont)
+ break;
+ else
+ charexists = 0;
+ }
+
+ if (utf8strlen) {
+ if (render) {
+ ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
+ XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
+ usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen);
+ }
+ x += ew;
+ w -= ew;
+ }
+ if (render && overflow)
+ drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
+
+ if (!*text || overflow) {
+ break;
+ } else if (nextfont) {
+ charexists = 0;
+ usedfont = nextfont;
+ } else {
+ /* Regardless of whether or not a fallback font is found, the
+ * character must be drawn. */
+ charexists = 1;
+
+ for (i = 0; i < nomatches_len; ++i) {
+ /* avoid calling XftFontMatch if we know we won't find a match */
+ if (utf8codepoint == nomatches.codepoint[i])
+ goto no_match;
+ }
+
+ fccharset = FcCharSetCreate();
+ FcCharSetAddChar(fccharset, utf8codepoint);
+
+ if (!drw->fonts->pattern) {
+ /* Refer to the comment in xfont_create for more information. */
+ die("the first font in the cache must be loaded from a font string.");
+ }
+
+ fcpattern = FcPatternDuplicate(drw->fonts->pattern);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
+
+ FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+ match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
+
+ FcCharSetDestroy(fccharset);
+ FcPatternDestroy(fcpattern);
+
+ if (match) {
+ usedfont = xfont_create(drw, NULL, match);
+ if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
+ for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
+ ; /* NOP */
+ curfont->next = usedfont;
+ } else {
+ xfont_free(usedfont);
+ nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint;
+no_match:
+ usedfont = drw->fonts;
+ }
+ }
+ }
+ }
+ if (d)
+ XftDrawDestroy(d);
+
+ return x + (render ? w : 0);
+}
+
+void
+drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
+ XSync(drw->dpy, False);
+}
+
+unsigned int
+drw_fontset_getwidth(Drw *drw, const char *text)
+{
+ if (!drw || !drw->fonts || !text)
+ return 0;
+ return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
+}
+
+unsigned int
+drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n)
+{
+ unsigned int tmp = 0;
+ if (drw && drw->fonts && text && n)
+ tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n);
+ return MIN(n, tmp);
+}
+
+void
+drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
+{
+ XGlyphInfo ext;
+
+ if (!font || !text)
+ return;
+
+ XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
+ if (w)
+ *w = ext.xOff;
+ if (h)
+ *h = font->h;
+}
+
+Cur *
+drw_cur_create(Drw *drw, int shape)
+{
+ Cur *cur;
+
+ if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
+ return NULL;
+
+ cur->cursor = XCreateFontCursor(drw->dpy, shape);
+
+ return cur;
+}
+
+void
+drw_cur_free(Drw *drw, Cur *cursor)
+{
+ if (!cursor)
+ return;
+
+ XFreeCursor(drw->dpy, cursor->cursor);
+ free(cursor);
+}
diff --git a/dwmo/drw.h b/dwmo/drw.h
new file mode 100755
index 0000000..6471431
--- /dev/null
+++ b/dwmo/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/dwmo/drw.o b/dwmo/drw.o
new file mode 100644
index 0000000..c94c785
--- /dev/null
+++ b/dwmo/drw.o
Binary files differ
diff --git a/dwmo/dwm b/dwmo/dwm
new file mode 100755
index 0000000..f4121dd
--- /dev/null
+++ b/dwmo/dwm
Binary files differ
diff --git a/dwmo/dwm.1 b/dwmo/dwm.1
new file mode 100755
index 0000000..7b6cadb
--- /dev/null
+++ b/dwmo/dwm.1
@@ -0,0 +1,186 @@
+.TH DWM 1 dwm\-VERSION
+.SH NAME
+dwm \- dynamic window manager
+.SH SYNOPSIS
+.B dwm
+.RB [ \-v ]
+.SH DESCRIPTION
+dwm is a dynamic window manager for X. It manages windows in tiled, monocle
+and floating layouts. Either layout can be applied dynamically, optimising the
+environment for the application in use and the task performed.
+.P
+In tiled layouts windows are managed in a master and stacking area. The master
+area on the left contains one window by default, and the stacking area on the
+right contains all other windows. The number of master area windows can be
+adjusted from zero to an arbitrary number. In monocle layout all windows are
+maximised to the screen size. In floating layout windows can be resized and
+moved freely. Dialog windows are always managed floating, regardless of the
+layout applied.
+.P
+Windows are grouped by tags. Each window can be tagged with one or multiple
+tags. Selecting certain tags displays all windows with these tags.
+.P
+Each screen contains a small status bar which displays all available tags, the
+layout, the title of the focused window, and the text read from the root window
+name property, if the screen is focused. A floating window is indicated with an
+empty square and a maximised floating window is indicated with a filled square
+before the windows title. The selected tags are indicated with a different
+color. The tags of the focused window are indicated with a filled square in the
+top left corner. The tags which are applied to one or more windows are
+indicated with an empty square in the top left corner.
+.P
+dwm draws a small border around windows to indicate the focus state.
+.SH OPTIONS
+.TP
+.B \-v
+prints version information to stderr, then exits.
+.SH USAGE
+.SS Status bar
+.TP
+.B X root window name
+is read and displayed in the status text area. It can be set with the
+.BR xsetroot (1)
+command.
+.TP
+.B Button1
+click on a tag label to display all windows with that tag, click on the layout
+label toggles between tiled and floating layout.
+.TP
+.B Button3
+click on a tag label adds/removes all windows with that tag to/from the view.
+.TP
+.B Mod1\-Button1
+click on a tag label applies that tag to the focused window.
+.TP
+.B Mod1\-Button3
+click on a tag label adds/removes that tag to/from the focused window.
+.SS Keyboard commands
+.TP
+.B Mod1\-Shift\-Return
+Start
+.BR st(1).
+.TP
+.B Mod1\-p
+Spawn
+.BR dmenu(1)
+for launching other programs.
+.TP
+.B Mod1\-,
+Focus previous screen, if any.
+.TP
+.B Mod1\-.
+Focus next screen, if any.
+.TP
+.B Mod1\-Shift\-,
+Send focused window to previous screen, if any.
+.TP
+.B Mod1\-Shift\-.
+Send focused window to next screen, if any.
+.TP
+.B Mod1\-b
+Toggles bar on and off.
+.TP
+.B Mod1\-t
+Sets tiled layout.
+.TP
+.B Mod1\-f
+Sets floating layout.
+.TP
+.B Mod1\-m
+Sets monocle layout.
+.TP
+.B Mod1\-space
+Toggles between current and previous layout.
+.TP
+.B Mod1\-j
+Focus next window.
+.TP
+.B Mod1\-k
+Focus previous window.
+.TP
+.B Mod1\-i
+Increase number of windows in master area.
+.TP
+.B Mod1\-d
+Decrease number of windows in master area.
+.TP
+.B Mod1\-l
+Increase master area size.
+.TP
+.B Mod1\-h
+Decrease master area size.
+.TP
+.B Mod1\-Return
+Zooms/cycles focused window to/from master area (tiled layouts only).
+.TP
+.B Mod1\-Shift\-c
+Close focused window.
+.TP
+.B Mod1\-Shift\-space
+Toggle focused window between tiled and floating state.
+.TP
+.B Mod1\-Tab
+Toggles to the previously selected tags.
+.TP
+.B Mod1\-Shift\-[1..n]
+Apply nth tag to focused window.
+.TP
+.B Mod1\-Shift\-0
+Apply all tags to focused window.
+.TP
+.B Mod1\-Control\-Shift\-[1..n]
+Add/remove nth tag to/from focused window.
+.TP
+.B Mod1\-[1..n]
+View all windows with nth tag.
+.TP
+.B Mod1\-0
+View all windows with any tag.
+.TP
+.B Mod1\-Control\-[1..n]
+Add/remove all windows with nth tag to/from the view.
+.TP
+.B Mod1\-Shift\-q
+Quit dwm.
+.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/dwmo/dwm.c b/dwmo/dwm.c
new file mode 100755
index 0000000..31937ef
--- /dev/null
+++ b/dwmo/dwm.c
@@ -0,0 +1,2196 @@
+/* See LICENSE file for copyright and license details.
+ *
+ * dynamic window manager is designed like any other X client as well. It is
+ * driven through handling X events. In contrast to other X clients, a window
+ * manager selects for SubstructureRedirectMask on the root window, to receive
+ * events about window (dis-)appearance. Only one X connection at a time is
+ * allowed to select for this event mask.
+ *
+ * The event handlers of dwm are organized in an array which is accessed
+ * whenever a new event has been fetched. This allows event dispatching
+ * in O(1) time.
+ *
+ * Each child of the root window is called a client, except windows which have
+ * set the override_redirect flag. Clients are organized in a linked client
+ * list on each monitor, the focus history is remembered through a stack list
+ * on each monitor. Each client contains a bit array to indicate the tags of a
+ * client.
+ *
+ * Keys and tagging rules are organized as arrays and defined in config.h.
+ *
+ * To understand everything else, start reading main().
+ */
+#include <errno.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <X11/cursorfont.h>
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xproto.h>
+#include <X11/Xutil.h>
+#include <X11/XF86keysym.h>
+#ifdef XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif /* XINERAMA */
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+#include "util.h"
+
+/* macros */
+#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask)
+#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask))
+#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \
+ * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy)))
+#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags]))
+#define LENGTH(X) (sizeof X / sizeof X[0])
+#define MOUSEMASK (BUTTONMASK|PointerMotionMask)
+#define WIDTH(X) ((X)->w + 2 * (X)->bw)
+#define HEIGHT(X) ((X)->h + 2 * (X)->bw)
+#define TAGMASK ((1 << LENGTH(tags)) - 1)
+#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
+/* enums */
+enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */
+enum { SchemeNorm, SchemeSel }; /* color schemes */
+enum { NetSupported, NetWMName, NetWMState, NetWMCheck,
+ NetWMFullscreen, NetActiveWindow, NetWMWindowType,
+ NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */
+enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */
+enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,
+ ClkClientWin, ClkRootWin, ClkLast }; /* clicks */
+
+typedef union {
+ int i;
+ unsigned int ui;
+ float f;
+ const void *v;
+} Arg;
+
+typedef struct {
+ unsigned int click;
+ unsigned int mask;
+ unsigned int button;
+ void (*func)(const Arg *arg);
+ const Arg arg;
+} Button;
+
+typedef struct Monitor Monitor;
+typedef struct Client Client;
+struct Client {
+ char name[256];
+ float mina, maxa;
+ int x, y, w, h;
+ int oldx, oldy, oldw, oldh;
+ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;
+ int bw, oldbw;
+ unsigned int tags;
+ int isfixed, iscentered, 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 */
+ 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 iscentered;
+ 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 setfocus(Client *c);
+static void setfullscreen(Client *c, int fullscreen);
+static void setlayout(const Arg *arg);
+static void setmfact(const Arg *arg);
+static void setup(void);
+static void seturgent(Client *c, int urg);
+static void showhide(Client *c);
+static void sigchld(int unused);
+static void sighup(int unused);
+static void sigterm(int unused);
+static void spawn(const Arg *arg);
+static 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->iscentered = 0;
+ 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->iscentered = r->iscentered;
+ 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;
+ unsigned int occ = 0;
+ for(c = m->clients; c; c=c->next)
+ occ |= c->tags;
+ do {
+ /* Do not reserve space for vacant tags */
+ if (!(occ & 1 << i || m->tagset[m->seltags] & 1 << i))
+ continue;
+ 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->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++) {
+ /* Do not draw vacant tags */
+ if(!(occ & 1 << i || m->tagset[m->seltags] & 1 << i))
+ continue;
+ 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);
+ 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, k;
+ unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
+ int start, end, skip;
+ KeySym *syms;
+
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ XDisplayKeycodes(dpy, &start, &end);
+ syms = XGetKeyboardMapping(dpy, start, end - start + 1, &skip);
+ if (!syms)
+ return;
+ for (k = start; k <= end; k++)
+ for (i = 0; i < LENGTH(keys); i++)
+ /* skip modifier codes, we do that ourselves */
+ if (keys[i].keysym == syms[(k - start) * skip])
+ for (j = 0; j < LENGTH(modifiers); j++)
+ XGrabKey(dpy, k,
+ keys[i].mod | modifiers[j],
+ root, True,
+ GrabModeAsync, GrabModeAsync);
+ XFree(syms);
+ }
+}
+
+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);
+ if (c->iscentered) {
+ c->x = c->mon->mx + (c->mon->mw - WIDTH(c)) / 2;
+ c->y = c->mon->my + (c->mon->mh - HEIGHT(c)) / 2;
+ }
+ XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask);
+ grabbuttons(c, 0);
+ if (!c->isfloating)
+ c->isfloating = c->oldstate = trans != None || c->isfixed;
+ if (c->isfloating)
+ XRaiseWindow(dpy, c->win);
+ attach(c);
+ attachstack(c);
+ XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend,
+ (unsigned char *) &(c->win), 1);
+ XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */
+ setclientstate(c, NormalState);
+ if (c->mon == selmon)
+ unfocus(selmon->sel, 0);
+ c->mon->sel = c;
+ arrange(c->mon);
+ XMapWindow(dpy, c->win);
+ focus(NULL);
+}
+
+void
+mappingnotify(XEvent *e)
+{
+ XMappingEvent *ev = &e->xmapping;
+
+ XRefreshKeyboardMapping(ev);
+ if (ev->request == MappingKeyboard)
+ grabkeys();
+}
+
+void
+maprequest(XEvent *e)
+{
+ static XWindowAttributes wa;
+ XMapRequestEvent *ev = &e->xmaprequest;
+
+ 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);
+ 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
+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);
+ /* init cursors */
+ cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr);
+ cursor[CurResize] = drw_cur_create(drw, XC_sizing);
+ cursor[CurMove] = drw_cur_create(drw, XC_fleur);
+ /* init appearance */
+ scheme = ecalloc(LENGTH(colors), sizeof(Clr *));
+ for (i = 0; i < LENGTH(colors); i++)
+ scheme[i] = drw_scm_create(drw, colors[i], 3);
+ /* init bars */
+ updatebars();
+ updatestatus();
+ /* supporting window for NetWMCheck */
+ wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
+ XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32,
+ PropModeReplace, (unsigned char *) &wmcheckwin, 1);
+ XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8,
+ PropModeReplace, (unsigned char *) "dwm", 3);
+ XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32,
+ PropModeReplace, (unsigned char *) &wmcheckwin, 1);
+ /* EWMH support per view */
+ XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) netatom, NetLast);
+ XDeleteProperty(dpy, root, netatom[NetClientList]);
+ /* select events */
+ wa.cursor = cursor[CurNormal]->cursor;
+ wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask
+ |ButtonPressMask|PointerMotionMask|EnterWindowMask
+ |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask;
+ XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa);
+ XSelectInput(dpy, root, wa.event_mask);
+ grabkeys();
+ focus(NULL);
+}
+
+void
+seturgent(Client *c, int urg)
+{
+ XWMHints *wmh;
+
+ c->isurgent = urg;
+ if (!(wmh = XGetWMHints(dpy, c->win)))
+ return;
+ wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint);
+ XSetWMHints(dpy, c->win, wmh);
+ XFree(wmh);
+}
+
+void
+showhide(Client *c)
+{
+ if (!c)
+ return;
+ if (ISVISIBLE(c)) {
+ /* show clients top down */
+ XMoveWindow(dpy, c->win, c->x, c->y);
+ if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen)
+ resize(c, c->x, c->y, c->w, c->h, 0);
+ showhide(c->snext);
+ } else {
+ /* hide clients bottom up */
+ showhide(c->snext);
+ XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y);
+ }
+}
+
+void
+sigchld(int unused)
+{
+ if (signal(SIGCHLD, sigchld) == SIG_ERR)
+ die("can't install SIGCHLD handler:");
+ while (0 < waitpid(-1, NULL, WNOHANG));
+}
+
+void
+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)
+ dmenumon[0] = '0' + selmon->num;
+ if (fork() == 0) {
+ if (dpy)
+ close(ConnectionNumber(dpy));
+ setsid();
+ execvp(((char **)arg->v)[0], (char **)arg->v);
+ die("dwm: execvp '%s' failed:", ((char **)arg->v)[0]);
+ }
+}
+
+void
+tag(const Arg *arg)
+{
+ if (selmon->sel && arg->ui & TAGMASK) {
+ selmon->sel->tags = arg->ui & TAGMASK;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+tagmon(const Arg *arg)
+{
+ if (!selmon->sel || !mons->next)
+ return;
+ sendmon(selmon->sel, dirtomon(arg->i));
+}
+
+void
+tile(Monitor *m)
+{
+ unsigned int i, n, h, mw, my, ty;
+ Client *c;
+
+ for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++);
+ if (n == 0)
+ return;
+
+ if (n > m->nmaster)
+ mw = m->nmaster ? m->ww * m->mfact : 0;
+ else
+ mw = m->ww;
+ for (i = my = ty = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++)
+ if (i < m->nmaster) {
+ h = (m->wh - my) / (MIN(n, m->nmaster) - i);
+ resize(c, m->wx, m->wy + my, mw - (2*c->bw), h - (2*c->bw), 0);
+ if (my + HEIGHT(c) < m->wh)
+ my += HEIGHT(c);
+ } else {
+ h = (m->wh - ty) / (n - i);
+ resize(c, m->wx + mw, m->wy + ty, m->ww - mw - (2*c->bw), h - (2*c->bw), 0);
+ if (ty + HEIGHT(c) < m->wh)
+ ty += HEIGHT(c);
+ }
+}
+
+void
+togglebar(const Arg *arg)
+{
+ selmon->showbar = !selmon->showbar;
+ updatebarpos(selmon);
+ XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh);
+ arrange(selmon);
+}
+
+void
+togglefloating(const Arg *arg)
+{
+ if (!selmon->sel)
+ return;
+ if (selmon->sel->isfullscreen) /* no support for fullscreen windows */
+ return;
+ selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed;
+ if (selmon->sel->isfloating)
+ resize(selmon->sel, selmon->sel->x, selmon->sel->y,
+ selmon->sel->w, selmon->sel->h, 0);
+ arrange(selmon);
+}
+
+void
+toggletag(const Arg *arg)
+{
+ unsigned int newtags;
+
+ if (!selmon->sel)
+ return;
+ newtags = selmon->sel->tags ^ (arg->ui & TAGMASK);
+ if (newtags) {
+ selmon->sel->tags = newtags;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+toggleview(const Arg *arg)
+{
+ unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK);
+
+ if (newtagset) {
+ selmon->tagset[selmon->seltags] = newtagset;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+unfocus(Client *c, int setfocus)
+{
+ if (!c)
+ return;
+ grabbuttons(c, 0);
+ XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel);
+ if (setfocus) {
+ XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+ }
+}
+
+void
+unmanage(Client *c, int destroyed)
+{
+ Monitor *m = c->mon;
+ XWindowChanges wc;
+
+ detach(c);
+ detachstack(c);
+ if (!destroyed) {
+ wc.border_width = c->oldbw;
+ XGrabServer(dpy); /* avoid race conditions */
+ XSetErrorHandler(xerrordummy);
+ 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->iscentered = 1;
+ 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/dwmo/dwm.c.orig b/dwmo/dwm.c.orig
new file mode 100755
index 0000000..0d11714
--- /dev/null
+++ b/dwmo/dwm.c.orig
@@ -0,0 +1,2173 @@
+/* See LICENSE file for copyright and license details.
+ *
+ * dynamic window manager is designed like any other X client as well. It is
+ * driven through handling X events. In contrast to other X clients, a window
+ * manager selects for SubstructureRedirectMask on the root window, to receive
+ * events about window (dis-)appearance. Only one X connection at a time is
+ * allowed to select for this event mask.
+ *
+ * The event handlers of dwm are organized in an array which is accessed
+ * whenever a new event has been fetched. This allows event dispatching
+ * in O(1) time.
+ *
+ * Each child of the root window is called a client, except windows which have
+ * set the override_redirect flag. Clients are organized in a linked client
+ * list on each monitor, the focus history is remembered through a stack list
+ * on each monitor. Each client contains a bit array to indicate the tags of a
+ * client.
+ *
+ * Keys and tagging rules are organized as arrays and defined in config.h.
+ *
+ * To understand everything else, start reading main().
+ */
+#include <errno.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <X11/cursorfont.h>
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xproto.h>
+#include <X11/Xutil.h>
+#ifdef XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif /* XINERAMA */
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+#include "util.h"
+
+/* macros */
+#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask)
+#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask))
+#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \
+ * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy)))
+#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags]))
+#define LENGTH(X) (sizeof X / sizeof X[0])
+#define MOUSEMASK (BUTTONMASK|PointerMotionMask)
+#define WIDTH(X) ((X)->w + 2 * (X)->bw)
+#define HEIGHT(X) ((X)->h + 2 * (X)->bw)
+#define TAGMASK ((1 << LENGTH(tags)) - 1)
+#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
+/* enums */
+enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */
+enum { SchemeNorm, SchemeSel }; /* color schemes */
+enum { NetSupported, NetWMName, NetWMState, NetWMCheck,
+ NetWMFullscreen, NetActiveWindow, NetWMWindowType,
+ NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */
+enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */
+enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,
+ ClkClientWin, ClkRootWin, ClkLast }; /* clicks */
+
+typedef union {
+ int i;
+ unsigned int ui;
+ float f;
+ const void *v;
+} Arg;
+
+typedef struct {
+ unsigned int click;
+ unsigned int mask;
+ unsigned int button;
+ void (*func)(const Arg *arg);
+ const Arg arg;
+} Button;
+
+typedef struct Monitor Monitor;
+typedef struct Client Client;
+struct Client {
+ char name[256];
+ float mina, maxa;
+ int x, y, w, h;
+ int oldx, oldy, oldw, oldh;
+ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;
+ int bw, oldbw;
+ unsigned int tags;
+ int isfixed, iscentered, 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 */
+ 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 iscentered;
+ 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 setfocus(Client *c);
+static void setfullscreen(Client *c, int fullscreen);
+static void setlayout(const Arg *arg);
+static void setmfact(const Arg *arg);
+static void setup(void);
+static void seturgent(Client *c, int urg);
+static void showhide(Client *c);
+static void sigchld(int unused);
+static void 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 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->iscentered = 0;
+ 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->iscentered = r->iscentered;
+ 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;
+ unsigned int occ = 0;
+ for(c = m->clients; c; c=c->next)
+ occ |= c->tags;
+ do {
+ /* Do not reserve space for vacant tags */
+ if (!(occ & 1 << i || m->tagset[m->seltags] & 1 << i))
+ continue;
+ 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->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++) {
+ /* Do not draw vacant tags */
+ if(!(occ & 1 << i || m->tagset[m->seltags] & 1 << i))
+ continue;
+ 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);
+ 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, k;
+ unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
+ int start, end, skip;
+ KeySym *syms;
+
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ XDisplayKeycodes(dpy, &start, &end);
+ syms = XGetKeyboardMapping(dpy, start, end - start + 1, &skip);
+ if (!syms)
+ return;
+ for (k = start; k <= end; k++)
+ for (i = 0; i < LENGTH(keys); i++)
+ /* skip modifier codes, we do that ourselves */
+ if (keys[i].keysym == syms[(k - start) * skip])
+ for (j = 0; j < LENGTH(modifiers); j++)
+ XGrabKey(dpy, k,
+ keys[i].mod | modifiers[j],
+ root, True,
+ GrabModeAsync, GrabModeAsync);
+ XFree(syms);
+ }
+}
+
+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);
+ if (c->iscentered) {
+ c->x = c->mon->mx + (c->mon->mw - WIDTH(c)) / 2;
+ c->y = c->mon->my + (c->mon->mh - HEIGHT(c)) / 2;
+ }
+ XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask);
+ grabbuttons(c, 0);
+ if (!c->isfloating)
+ c->isfloating = c->oldstate = trans != None || c->isfixed;
+ if (c->isfloating)
+ XRaiseWindow(dpy, c->win);
+ attach(c);
+ attachstack(c);
+ XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend,
+ (unsigned char *) &(c->win), 1);
+ XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */
+ setclientstate(c, NormalState);
+ if (c->mon == selmon)
+ unfocus(selmon->sel, 0);
+ c->mon->sel = c;
+ arrange(c->mon);
+ XMapWindow(dpy, c->win);
+ focus(NULL);
+}
+
+void
+mappingnotify(XEvent *e)
+{
+ XMappingEvent *ev = &e->xmapping;
+
+ XRefreshKeyboardMapping(ev);
+ if (ev->request == MappingKeyboard)
+ grabkeys();
+}
+
+void
+maprequest(XEvent *e)
+{
+ static XWindowAttributes wa;
+ XMapRequestEvent *ev = &e->xmaprequest;
+
+ 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)
+{
+ running = 0;
+}
+
+Monitor *
+recttomon(int x, int y, int w, int h)
+{
+ Monitor *m, *r = selmon;
+ int a, area = 0;
+
+ for (m = mons; m; m = m->next)
+ if ((a = INTERSECT(x, y, w, h, m)) > area) {
+ area = a;
+ r = m;
+ }
+ return r;
+}
+
+void
+resize(Client *c, int x, int y, int w, int h, int interact)
+{
+ if (applysizehints(c, &x, &y, &w, &h, interact))
+ resizeclient(c, x, y, w, h);
+}
+
+void
+resizeclient(Client *c, int x, int y, int w, int h)
+{
+ XWindowChanges wc;
+
+ c->oldx = c->x; c->x = wc.x = x;
+ c->oldy = c->y; c->y = wc.y = y;
+ c->oldw = c->w; c->w = wc.width = w;
+ c->oldh = c->h; c->h = wc.height = h;
+ wc.border_width = c->bw;
+ XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc);
+ configure(c);
+ XSync(dpy, False);
+}
+
+void
+resizemouse(const Arg *arg)
+{
+ int ocx, ocy, nw, nh;
+ Client *c;
+ Monitor *m;
+ XEvent ev;
+ Time lasttime = 0;
+
+ if (!(c = selmon->sel))
+ return;
+ if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */
+ return;
+ restack(selmon);
+ ocx = c->x;
+ ocy = c->y;
+ if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
+ None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess)
+ return;
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1);
+ do {
+ XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
+ switch(ev.type) {
+ case ConfigureRequest:
+ case Expose:
+ case MapRequest:
+ handler[ev.type](&ev);
+ break;
+ case MotionNotify:
+ if ((ev.xmotion.time - lasttime) <= (1000 / 60))
+ continue;
+ lasttime = ev.xmotion.time;
+
+ nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1);
+ nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1);
+ if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww
+ && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh)
+ {
+ if (!c->isfloating && selmon->lt[selmon->sellt]->arrange
+ && (abs(nw - c->w) > snap || abs(nh - c->h) > snap))
+ togglefloating(NULL);
+ }
+ if (!selmon->lt[selmon->sellt]->arrange || c->isfloating)
+ resize(c, c->x, c->y, nw, nh, 1);
+ break;
+ }
+ } while (ev.type != ButtonRelease);
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1);
+ XUngrabPointer(dpy, CurrentTime);
+ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
+ if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) {
+ sendmon(c, m);
+ selmon = m;
+ focus(NULL);
+ }
+}
+
+void
+restack(Monitor *m)
+{
+ Client *c;
+ XEvent ev;
+ XWindowChanges wc;
+
+ drawbar(m);
+ if (!m->sel)
+ return;
+ if (m->sel->isfloating || !m->lt[m->sellt]->arrange)
+ XRaiseWindow(dpy, m->sel->win);
+ if (m->lt[m->sellt]->arrange) {
+ wc.stack_mode = Below;
+ wc.sibling = m->barwin;
+ for (c = m->stack; c; c = c->snext)
+ if (!c->isfloating && ISVISIBLE(c)) {
+ XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc);
+ wc.sibling = c->win;
+ }
+ }
+ XSync(dpy, False);
+ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
+}
+
+void
+run(void)
+{
+ XEvent ev;
+ /* main event loop */
+ XSync(dpy, False);
+ while (running && !XNextEvent(dpy, &ev))
+ if (handler[ev.type])
+ handler[ev.type](&ev); /* call handler */
+}
+
+void
+scan(void)
+{
+ unsigned int i, num;
+ Window d1, d2, *wins = NULL;
+ XWindowAttributes wa;
+
+ if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
+ for (i = 0; i < num; i++) {
+ if (!XGetWindowAttributes(dpy, wins[i], &wa)
+ || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1))
+ continue;
+ if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)
+ manage(wins[i], &wa);
+ }
+ for (i = 0; i < num; i++) { /* now the transients */
+ if (!XGetWindowAttributes(dpy, wins[i], &wa))
+ continue;
+ if (XGetTransientForHint(dpy, wins[i], &d1)
+ && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState))
+ manage(wins[i], &wa);
+ }
+ if (wins)
+ XFree(wins);
+ }
+}
+
+void
+sendmon(Client *c, Monitor *m)
+{
+ if (c->mon == m)
+ return;
+ unfocus(c, 1);
+ detach(c);
+ detachstack(c);
+ c->mon = m;
+ c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */
+ attach(c);
+ attachstack(c);
+ focus(NULL);
+ arrange(NULL);
+}
+
+void
+setclientstate(Client *c, long state)
+{
+ long data[] = { state, None };
+
+ XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32,
+ PropModeReplace, (unsigned char *)data, 2);
+}
+
+int
+sendevent(Client *c, Atom proto)
+{
+ int n;
+ Atom *protocols;
+ int exists = 0;
+ XEvent ev;
+
+ if (XGetWMProtocols(dpy, c->win, &protocols, &n)) {
+ while (!exists && n--)
+ exists = protocols[n] == proto;
+ XFree(protocols);
+ }
+ if (exists) {
+ ev.type = ClientMessage;
+ ev.xclient.window = c->win;
+ ev.xclient.message_type = wmatom[WMProtocols];
+ ev.xclient.format = 32;
+ ev.xclient.data.l[0] = proto;
+ ev.xclient.data.l[1] = CurrentTime;
+ XSendEvent(dpy, c->win, False, NoEventMask, &ev);
+ }
+ return exists;
+}
+
+void
+setfocus(Client *c)
+{
+ if (!c->neverfocus) {
+ XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
+ XChangeProperty(dpy, root, netatom[NetActiveWindow],
+ XA_WINDOW, 32, PropModeReplace,
+ (unsigned char *) &(c->win), 1);
+ }
+ sendevent(c, wmatom[WMTakeFocus]);
+}
+
+void
+setfullscreen(Client *c, int fullscreen)
+{
+ if (fullscreen && !c->isfullscreen) {
+ XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
+ PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1);
+ c->isfullscreen = 1;
+ c->oldstate = c->isfloating;
+ c->oldbw = c->bw;
+ c->bw = 0;
+ c->isfloating = 1;
+ resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh);
+ XRaiseWindow(dpy, c->win);
+ } else if (!fullscreen && c->isfullscreen){
+ XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
+ PropModeReplace, (unsigned char*)0, 0);
+ c->isfullscreen = 0;
+ c->isfloating = c->oldstate;
+ c->bw = c->oldbw;
+ c->x = c->oldx;
+ c->y = c->oldy;
+ c->w = c->oldw;
+ c->h = c->oldh;
+ resizeclient(c, c->x, c->y, c->w, c->h);
+ arrange(c->mon);
+ }
+}
+
+void
+setlayout(const Arg *arg)
+{
+ if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt])
+ selmon->sellt ^= 1;
+ if (arg && arg->v)
+ selmon->lt[selmon->sellt] = (Layout *)arg->v;
+ strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol);
+ if (selmon->sel)
+ arrange(selmon);
+ else
+ drawbar(selmon);
+}
+
+/* arg > 1.0 will set mfact absolutely */
+void
+setmfact(const Arg *arg)
+{
+ float f;
+
+ if (!arg || !selmon->lt[selmon->sellt]->arrange)
+ return;
+ f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0;
+ if (f < 0.05 || f > 0.95)
+ return;
+ selmon->mfact = f;
+ arrange(selmon);
+}
+
+void
+setup(void)
+{
+ int i;
+ XSetWindowAttributes wa;
+ Atom utf8string;
+
+ /* clean up any zombies immediately */
+ sigchld(0);
+
+ /* init screen */
+ screen = DefaultScreen(dpy);
+ sw = DisplayWidth(dpy, screen);
+ sh = DisplayHeight(dpy, screen);
+ root = RootWindow(dpy, screen);
+ drw = drw_create(dpy, screen, root, sw, sh);
+ if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+ bh = drw->fonts->h + 2;
+ updategeom();
+ /* init atoms */
+ utf8string = XInternAtom(dpy, "UTF8_STRING", False);
+ wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
+ wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+ wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False);
+ wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
+ netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
+ netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
+ netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
+ netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False);
+ netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False);
+ netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
+ netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
+ netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
+ netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
+ /* init cursors */
+ cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr);
+ cursor[CurResize] = drw_cur_create(drw, XC_sizing);
+ cursor[CurMove] = drw_cur_create(drw, XC_fleur);
+ /* init appearance */
+ scheme = ecalloc(LENGTH(colors), sizeof(Clr *));
+ for (i = 0; i < LENGTH(colors); i++)
+ scheme[i] = drw_scm_create(drw, colors[i], 3);
+ /* init bars */
+ updatebars();
+ updatestatus();
+ /* supporting window for NetWMCheck */
+ wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
+ XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32,
+ PropModeReplace, (unsigned char *) &wmcheckwin, 1);
+ XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8,
+ PropModeReplace, (unsigned char *) "dwm", 3);
+ XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32,
+ PropModeReplace, (unsigned char *) &wmcheckwin, 1);
+ /* EWMH support per view */
+ XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) netatom, NetLast);
+ XDeleteProperty(dpy, root, netatom[NetClientList]);
+ /* select events */
+ wa.cursor = cursor[CurNormal]->cursor;
+ wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask
+ |ButtonPressMask|PointerMotionMask|EnterWindowMask
+ |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask;
+ XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa);
+ XSelectInput(dpy, root, wa.event_mask);
+ grabkeys();
+ focus(NULL);
+}
+
+void
+seturgent(Client *c, int urg)
+{
+ XWMHints *wmh;
+
+ c->isurgent = urg;
+ if (!(wmh = XGetWMHints(dpy, c->win)))
+ return;
+ wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint);
+ XSetWMHints(dpy, c->win, wmh);
+ XFree(wmh);
+}
+
+void
+showhide(Client *c)
+{
+ if (!c)
+ return;
+ if (ISVISIBLE(c)) {
+ /* show clients top down */
+ XMoveWindow(dpy, c->win, c->x, c->y);
+ if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen)
+ resize(c, c->x, c->y, c->w, c->h, 0);
+ showhide(c->snext);
+ } else {
+ /* hide clients bottom up */
+ showhide(c->snext);
+ XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y);
+ }
+}
+
+void
+sigchld(int unused)
+{
+ if (signal(SIGCHLD, sigchld) == SIG_ERR)
+ die("can't install SIGCHLD handler:");
+ while (0 < waitpid(-1, NULL, WNOHANG));
+}
+
+void
+spawn(const Arg *arg)
+{
+ if (arg->v == dmenucmd)
+ dmenumon[0] = '0' + selmon->num;
+ if (fork() == 0) {
+ if (dpy)
+ close(ConnectionNumber(dpy));
+ setsid();
+ execvp(((char **)arg->v)[0], (char **)arg->v);
+ die("dwm: execvp '%s' failed:", ((char **)arg->v)[0]);
+ }
+}
+
+void
+tag(const Arg *arg)
+{
+ if (selmon->sel && arg->ui & TAGMASK) {
+ selmon->sel->tags = arg->ui & TAGMASK;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+tagmon(const Arg *arg)
+{
+ if (!selmon->sel || !mons->next)
+ return;
+ sendmon(selmon->sel, dirtomon(arg->i));
+}
+
+void
+tile(Monitor *m)
+{
+ unsigned int i, n, h, mw, my, ty;
+ Client *c;
+
+ for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++);
+ if (n == 0)
+ return;
+
+ if (n > m->nmaster)
+ mw = m->nmaster ? m->ww * m->mfact : 0;
+ else
+ mw = m->ww;
+ for (i = my = ty = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++)
+ if (i < m->nmaster) {
+ h = (m->wh - my) / (MIN(n, m->nmaster) - i);
+ resize(c, m->wx, m->wy + my, mw - (2*c->bw), h - (2*c->bw), 0);
+ if (my + HEIGHT(c) < m->wh)
+ my += HEIGHT(c);
+ } else {
+ h = (m->wh - ty) / (n - i);
+ resize(c, m->wx + mw, m->wy + ty, m->ww - mw - (2*c->bw), h - (2*c->bw), 0);
+ if (ty + HEIGHT(c) < m->wh)
+ ty += HEIGHT(c);
+ }
+}
+
+void
+togglebar(const Arg *arg)
+{
+ selmon->showbar = !selmon->showbar;
+ updatebarpos(selmon);
+ XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh);
+ arrange(selmon);
+}
+
+void
+togglefloating(const Arg *arg)
+{
+ if (!selmon->sel)
+ return;
+ if (selmon->sel->isfullscreen) /* no support for fullscreen windows */
+ return;
+ selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed;
+ if (selmon->sel->isfloating)
+ resize(selmon->sel, selmon->sel->x, selmon->sel->y,
+ selmon->sel->w, selmon->sel->h, 0);
+ arrange(selmon);
+}
+
+void
+toggletag(const Arg *arg)
+{
+ unsigned int newtags;
+
+ if (!selmon->sel)
+ return;
+ newtags = selmon->sel->tags ^ (arg->ui & TAGMASK);
+ if (newtags) {
+ selmon->sel->tags = newtags;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+toggleview(const Arg *arg)
+{
+ unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK);
+
+ if (newtagset) {
+ selmon->tagset[selmon->seltags] = newtagset;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+unfocus(Client *c, int setfocus)
+{
+ if (!c)
+ return;
+ grabbuttons(c, 0);
+ XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel);
+ if (setfocus) {
+ XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+ }
+}
+
+void
+unmanage(Client *c, int destroyed)
+{
+ Monitor *m = c->mon;
+ XWindowChanges wc;
+
+ detach(c);
+ detachstack(c);
+ if (!destroyed) {
+ wc.border_width = c->oldbw;
+ XGrabServer(dpy); /* avoid race conditions */
+ XSetErrorHandler(xerrordummy);
+ 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->iscentered = 1;
+ 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();
+ cleanup();
+ XCloseDisplay(dpy);
+ return EXIT_SUCCESS;
+}
diff --git a/dwmo/dwm.o b/dwmo/dwm.o
new file mode 100644
index 0000000..b7b63a0
--- /dev/null
+++ b/dwmo/dwm.o
Binary files differ
diff --git a/dwmo/dwm.png b/dwmo/dwm.png
new file mode 100755
index 0000000..b1f9ba7
--- /dev/null
+++ b/dwmo/dwm.png
Binary files differ
diff --git a/dwmo/patches/dwm-center-6.2.diff b/dwmo/patches/dwm-center-6.2.diff
new file mode 100755
index 0000000..0dfe156
--- /dev/null
+++ b/dwmo/patches/dwm-center-6.2.diff
@@ -0,0 +1,90 @@
+From 69f91089d9248fa9695eb925956e255a215171b8 Mon Sep 17 00:00:00 2001
+From: bakkeby <bakkeby@gmail.com>
+Date: Tue, 7 Apr 2020 12:29:08 +0200
+Subject: [PATCH] Adding 6.2 center patch with multi-monitor fix and
+ auto-centering of floating popup windows
+---
+ config.def.h | 6 +++---
+ dwm.c | 13 +++++++++++--
+ 2 files changed, 14 insertions(+), 5 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 1c0b587..44b46e5 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -26,9 +26,9 @@ static const Rule rules[] = {
+ * WM_CLASS(STRING) = instance, class
+ * WM_NAME(STRING) = title
+ */
+- /* class instance title tags mask isfloating monitor */
+- { "Gimp", NULL, NULL, 0, 1, -1 },
+- { "Firefox", NULL, NULL, 1 << 8, 0, -1 },
++ /* class instance title tags mask iscentered isfloating monitor */
++ { "Gimp", NULL, NULL, 0, 0, 1, -1 },
++ { "Firefox", NULL, NULL, 1 << 8, 0, 0, -1 },
+ };
+
+ /* layout(s) */
+diff --git a/dwm.c b/dwm.c
+index 4465af1..ab33757 100644
+--- a/dwm.c
++++ b/dwm.c
+@@ -92,7 +92,7 @@ struct Client {
+ int basew, baseh, incw, inch, maxw, maxh, minw, minh;
+ int bw, oldbw;
+ unsigned int tags;
+- int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
++ int isfixed, iscentered, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
+ Client *next;
+ Client *snext;
+ Monitor *mon;
+@@ -137,6 +137,7 @@ typedef struct {
+ const char *instance;
+ const char *title;
+ unsigned int tags;
++ int iscentered;
+ int isfloating;
+ int monitor;
+ } Rule;
+@@ -285,6 +286,7 @@ applyrules(Client *c)
+ XClassHint ch = { NULL, NULL };
+
+ /* rule matching */
++ c->iscentered = 0;
+ c->isfloating = 0;
+ c->tags = 0;
+ XGetClassHint(dpy, c->win, &ch);
+@@ -297,6 +299,7 @@ applyrules(Client *c)
+ && (!r->class || strstr(class, r->class))
+ && (!r->instance || strstr(instance, r->instance)))
+ {
++ c->iscentered = r->iscentered;
+ c->isfloating = r->isfloating;
+ c->tags |= r->tags;
+ for (m = mons; m && m->num != r->monitor; m = m->next);
+@@ -1056,6 +1059,10 @@ manage(Window w, XWindowAttributes *wa)
+ updatewindowtype(c);
+ updatesizehints(c);
+ updatewmhints(c);
++ if (c->iscentered) {
++ c->x = c->mon->mx + (c->mon->mw - WIDTH(c)) / 2;
++ c->y = c->mon->my + (c->mon->mh - HEIGHT(c)) / 2;
++ }
+ XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask);
+ grabbuttons(c, 0);
+ if (!c->isfloating)
+@@ -2009,8 +2016,10 @@ updatewindowtype(Client *c)
+
+ if (state == netatom[NetWMFullscreen])
+ setfullscreen(c, 1);
+- if (wtype == netatom[NetWMWindowTypeDialog])
++ if (wtype == netatom[NetWMWindowTypeDialog]) {
++ c->iscentered = 1;
+ c->isfloating = 1;
++ }
+ }
+
+ void
+--
+2.17.1
+
diff --git a/dwmo/patches/dwm-hide_vacant_tags-6.3.diff b/dwmo/patches/dwm-hide_vacant_tags-6.3.diff
new file mode 100755
index 0000000..0ccc7fc
--- /dev/null
+++ b/dwmo/patches/dwm-hide_vacant_tags-6.3.diff
@@ -0,0 +1,39 @@
+diff --git a/dwm.c b/dwm.c
+index a96f33c..f2da729 100644
+--- a/dwm.c
++++ b/dwm.c
+@@ -432,9 +432,15 @@ buttonpress(XEvent *e)
+ }
+ if (ev->window == selmon->barwin) {
+ i = x = 0;
+- do
++ unsigned int occ = 0;
++ for(c = m->clients; c; c=c->next)
++ occ |= c->tags;
++ do {
++ /* Do not reserve space for vacant tags */
++ if (!(occ & 1 << i || m->tagset[m->seltags] & 1 << i))
++ continue;
+ x += TEXTW(tags[i]);
+- while (ev->x >= x && ++i < LENGTH(tags));
++ } while (ev->x >= x && ++i < LENGTH(tags));
+ if (i < LENGTH(tags)) {
+ click = ClkTagBar;
+ arg.ui = 1 << i;
+@@ -719,13 +725,12 @@ drawbar(Monitor *m)
+ }
+ x = 0;
+ for (i = 0; i < LENGTH(tags); i++) {
++ /* Do not draw vacant tags */
++ if(!(occ & 1 << i || m->tagset[m->seltags] & 1 << i))
++ continue;
+ w = TEXTW(tags[i]);
+ drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i);
+- if (occ & 1 << i)
+- drw_rect(drw, x + boxs, boxs, boxw, boxw,
+- m == selmon && selmon->sel && selmon->sel->tags & 1 << i,
+- urg & 1 << i);
+ x += w;
+ }
+ w = blw = TEXTW(m->ltsymbol);
diff --git a/dwmo/patches/dwm-restartsig-20180523-6.2.diff b/dwmo/patches/dwm-restartsig-20180523-6.2.diff
new file mode 100755
index 0000000..f1f8680
--- /dev/null
+++ b/dwmo/patches/dwm-restartsig-20180523-6.2.diff
@@ -0,0 +1,139 @@
+From 2991f37f0aaf44b9f9b11e7893ff0af8eb88f649 Mon Sep 17 00:00:00 2001
+From: Christopher Drelich <cd@cdrakka.com>
+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/dwmo/transient.c b/dwmo/transient.c
new file mode 100755
index 0000000..040adb5
--- /dev/null
+++ b/dwmo/transient.c
@@ -0,0 +1,42 @@
+/* cc transient.c -o transient -lX11 */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+int main(void) {
+ Display *d;
+ Window r, f, t = None;
+ XSizeHints h;
+ XEvent e;
+
+ d = XOpenDisplay(NULL);
+ if (!d)
+ exit(1);
+ r = DefaultRootWindow(d);
+
+ f = XCreateSimpleWindow(d, r, 100, 100, 400, 400, 0, 0, 0);
+ h.min_width = h.max_width = h.min_height = h.max_height = 400;
+ h.flags = PMinSize | PMaxSize;
+ XSetWMNormalHints(d, f, &h);
+ XStoreName(d, f, "floating");
+ XMapWindow(d, f);
+
+ XSelectInput(d, f, ExposureMask);
+ while (1) {
+ XNextEvent(d, &e);
+
+ if (t == None) {
+ sleep(5);
+ t = XCreateSimpleWindow(d, r, 50, 50, 100, 100, 0, 0, 0);
+ XSetTransientForHint(d, t, f);
+ XStoreName(d, t, "transient");
+ XMapWindow(d, t);
+ XSelectInput(d, t, ExposureMask);
+ }
+ }
+
+ XCloseDisplay(d);
+ exit(0);
+}
diff --git a/dwmo/util.c b/dwmo/util.c
new file mode 100755
index 0000000..96b82c9
--- /dev/null
+++ b/dwmo/util.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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/dwmo/util.h b/dwmo/util.h
new file mode 100755
index 0000000..f633b51
--- /dev/null
+++ b/dwmo/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/dwmo/util.o b/dwmo/util.o
new file mode 100644
index 0000000..6cc164b
--- /dev/null
+++ b/dwmo/util.o
Binary files differ
diff --git a/install.sh b/install.sh
index 265b5d8..3333fa3 100755
--- a/install.sh
+++ b/install.sh
@@ -5,44 +5,35 @@ if [ "$EUID" -ne 0 ]
exit
fi
-mkdir /home/bt/.suckless
-cd /home/bt/.suckless
-
-git clone https://git.sr.ht/~bt/dwmo
cd dwmo
echo "Installing dwm"
rm -rf config.h
make clean install
cd ..
-git clone https://git.sr.ht/~bt/dmenuo
cd dmenuo
echo "Installing dmenu"
rm -rf config.h
make clean install
cd ..
-git clone https://git.sr.ht/~bt/slstatuso
cd slstatuso
echo "Installing slstatus"
rm -rf config.h
make clean install
cd ..
-git clone https://git.sr.ht/~bt/sto
cd sto
echo "Installing st"
rm -rf config.h
make clean install
cd ..
-git clone https://git.sr.ht/~bt/slocko
cd slocko
echo "Installing slock"
rm -rf config.h
make clean install
cd ..
-cd ..
rcctl enable apmd
rcctl start apmd
diff --git a/slocko/LICENSE b/slocko/LICENSE
new file mode 100644
index 0000000..2e4419b
--- /dev/null
+++ b/slocko/LICENSE
@@ -0,0 +1,24 @@
+MIT/X Consortium License
+
+© 2015-2016 Markus Teich <markus.teich@stusta.mhn.de>
+© 2014 Dimitris Papastamos <sin@2f30.org>
+© 2006-2014 Anselm R Garbe <anselm@garbe.us>
+© 2014-2016 Laslo Hunhold <dev@frign.de>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/slocko/Makefile b/slocko/Makefile
new file mode 100644
index 0000000..b20fd4e
--- /dev/null
+++ b/slocko/Makefile
@@ -0,0 +1,61 @@
+# slock - simple screen locker
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = slock.c ${COMPATSRC}
+OBJ = ${SRC:.c=.o}
+
+all: options slock
+
+options:
+ @echo slock build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+.c.o:
+ @echo CC $<
+ @${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.h config.mk arg.h util.h
+
+config.h:
+ @echo creating $@ from config.def.h
+ @cp config.def.h $@
+
+slock: ${OBJ}
+ @echo CC -o $@
+ @${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ @echo cleaning
+ @rm -f slock ${OBJ} slock-${VERSION}.tar.gz
+
+dist: clean
+ @echo creating dist tarball
+ @mkdir -p slock-${VERSION}
+ @cp -R LICENSE Makefile README slock.1 config.mk \
+ ${SRC} config.def.h arg.h util.h slock-${VERSION}
+ @tar -cf slock-${VERSION}.tar slock-${VERSION}
+ @gzip slock-${VERSION}.tar
+ @rm -rf slock-${VERSION}
+
+install: all
+ @echo installing executable file to ${DESTDIR}${PREFIX}/bin
+ @mkdir -p ${DESTDIR}${PREFIX}/bin
+ @cp -f slock ${DESTDIR}${PREFIX}/bin
+ @chmod 755 ${DESTDIR}${PREFIX}/bin/slock
+ @chmod u+s ${DESTDIR}${PREFIX}/bin/slock
+ @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
+ @mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ @sed "s/VERSION/${VERSION}/g" <slock.1 >${DESTDIR}${MANPREFIX}/man1/slock.1
+ @chmod 644 ${DESTDIR}${MANPREFIX}/man1/slock.1
+
+uninstall:
+ @echo removing executable file from ${DESTDIR}${PREFIX}/bin
+ @rm -f ${DESTDIR}${PREFIX}/bin/slock
+ @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
+ @rm -f ${DESTDIR}${MANPREFIX}/man1/slock.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/slocko/README b/slocko/README
new file mode 100644
index 0000000..3c225a6
--- /dev/null
+++ b/slocko/README
@@ -0,0 +1,25 @@
+slocko - simple screen locker for openbsd
+=========================================
+
+simple screen locker utility for X.
+
+
+Requirements
+------------
+In order to build slock you need the Xlib header files.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (slock is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install slock
+(if necessary as root):
+
+ make clean install
+
+
+Running slock
+-------------
+Simply invoke the 'slock' command. To get out of it, enter your password.
diff --git a/slocko/arg.h b/slocko/arg.h
new file mode 100644
index 0000000..0b23c53
--- /dev/null
+++ b/slocko/arg.h
@@ -0,0 +1,65 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][0] == '-'\
+ && argv[0][1];\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+/* Handles obsolete -NUM syntax */
+#define ARGNUM case '0':\
+ case '1':\
+ case '2':\
+ case '3':\
+ case '4':\
+ case '5':\
+ case '6':\
+ case '7':\
+ case '8':\
+ case '9'
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX))
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define LNGARG() &argv[0][0]
+
+#endif
diff --git a/slocko/config.def.h b/slocko/config.def.h
new file mode 100644
index 0000000..9855e21
--- /dev/null
+++ b/slocko/config.def.h
@@ -0,0 +1,12 @@
+/* user and group to drop privileges to */
+static const char *user = "nobody";
+static const char *group = "nogroup";
+
+static const char *colorname[NUMCOLS] = {
+ [INIT] = "black", /* after initialization */
+ [INPUT] = "#005577", /* during input */
+ [FAILED] = "#CC3333", /* wrong password */
+};
+
+/* treat a cleared input like a wrong password (color) */
+static const int failonclear = 1;
diff --git a/slocko/config.h b/slocko/config.h
new file mode 100644
index 0000000..9855e21
--- /dev/null
+++ b/slocko/config.h
@@ -0,0 +1,12 @@
+/* user and group to drop privileges to */
+static const char *user = "nobody";
+static const char *group = "nogroup";
+
+static const char *colorname[NUMCOLS] = {
+ [INIT] = "black", /* after initialization */
+ [INPUT] = "#005577", /* during input */
+ [FAILED] = "#CC3333", /* wrong password */
+};
+
+/* treat a cleared input like a wrong password (color) */
+static const int failonclear = 1;
diff --git a/slocko/config.mk b/slocko/config.mk
new file mode 100644
index 0000000..f7f8db3
--- /dev/null
+++ b/slocko/config.mk
@@ -0,0 +1,32 @@
+# slock version
+VERSION = 1.5
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+
+# includes and libs
+INCS = -I. -I/usr/include -I${X11INC}
+LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXext -lXrandr
+
+# flags
+CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE
+CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
+LDFLAGS = -s ${LIBS}
+COMPATSRC =
+
+# On OpenBSD and Darwin remove -lcrypt from LIBS
+#LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXext -lXrandr
+# On *BSD remove -DHAVE_SHADOW_H from CPPFLAGS
+# On NetBSD add -D_NETBSD_SOURCE to CPPFLAGS
+#CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE -D_NETBSD_SOURCE
+# On OpenBSD set COMPATSRC to empty
+#COMPATSRC =
+
+# compiler and linker
+CC = cc
diff --git a/slocko/explicit_bzero.c b/slocko/explicit_bzero.c
new file mode 100644
index 0000000..3e33ca8
--- /dev/null
+++ b/slocko/explicit_bzero.c
@@ -0,0 +1,19 @@
+/* $OpenBSD: explicit_bzero.c,v 1.3 2014/06/21 02:34:26 matthew Exp $ */
+/*
+ * Public domain.
+ * Written by Matthew Dempsky.
+ */
+
+#include <string.h>
+
+__attribute__((weak)) void
+__explicit_bzero_hook(void *buf, size_t len)
+{
+}
+
+void
+explicit_bzero(void *buf, size_t len)
+{
+ memset(buf, 0, len);
+ __explicit_bzero_hook(buf, len);
+}
diff --git a/slocko/explicit_bzero.o b/slocko/explicit_bzero.o
new file mode 100644
index 0000000..d1b8070
--- /dev/null
+++ b/slocko/explicit_bzero.o
Binary files differ
diff --git a/slocko/slock b/slocko/slock
new file mode 100755
index 0000000..3a52eb4
--- /dev/null
+++ b/slocko/slock
Binary files differ
diff --git a/slocko/slock.1 b/slocko/slock.1
new file mode 100644
index 0000000..82cdcd6
--- /dev/null
+++ b/slocko/slock.1
@@ -0,0 +1,39 @@
+.Dd 2016-08-23
+.Dt SLOCK 1
+.Sh NAME
+.Nm slock
+.Nd simple X screen locker
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Op Ar cmd Op Ar arg ...
+.Sh DESCRIPTION
+.Nm
+is a simple X screen locker. If provided,
+.Ar cmd Op Ar arg ...
+is executed after the screen has been locked.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl v
+Print version information to stdout and exit.
+.El
+.Sh SECURITY CONSIDERATIONS
+To make sure a locked screen can not be bypassed by switching VTs
+or killing the X server with Ctrl+Alt+Backspace, it is recommended
+to disable both in
+.Xr xorg.conf 5
+for maximum security:
+.Bd -literal -offset left
+Section "ServerFlags"
+ Option "DontVTSwitch" "True"
+ Option "DontZap" "True"
+EndSection
+.Ed
+.Sh EXAMPLES
+$
+.Nm
+/usr/sbin/s2ram
+.Sh CUSTOMIZATION
+.Nm
+can be customized by creating a custom config.h from config.def.h and
+(re)compiling the source code. This keeps it fast, secure and simple.
diff --git a/slocko/slock.c b/slocko/slock.c
new file mode 100644
index 0000000..5ae738c
--- /dev/null
+++ b/slocko/slock.c
@@ -0,0 +1,395 @@
+/* See LICENSE file for license details. */
+#define _XOPEN_SOURCE 500
+#if HAVE_SHADOW_H
+#include <shadow.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/keysym.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "arg.h"
+#include "util.h"
+
+char *argv0;
+
+enum {
+ INIT,
+ INPUT,
+ FAILED,
+ NUMCOLS
+};
+
+struct lock {
+ int screen;
+ Window root, win;
+ Pixmap pmap;
+ unsigned long colors[NUMCOLS];
+};
+
+struct xrandr {
+ int active;
+ int evbase;
+ int errbase;
+};
+
+#include "config.h"
+
+static void
+die(const char *errstr, ...)
+{
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(1);
+}
+
+#ifdef __linux__
+#include <fcntl.h>
+#include <linux/oom.h>
+
+static void
+dontkillme(void)
+{
+ FILE *f;
+ const char oomfile[] = "/proc/self/oom_score_adj";
+
+ if (!(f = fopen(oomfile, "w"))) {
+ if (errno == ENOENT)
+ return;
+ die("slock: fopen %s: %s\n", oomfile, strerror(errno));
+ }
+ fprintf(f, "%d", OOM_SCORE_ADJ_MIN);
+ if (fclose(f)) {
+ if (errno == EACCES)
+ die("slock: unable to disable OOM killer. "
+ "Make sure to suid or sgid slock.\n");
+ else
+ die("slock: fclose %s: %s\n", oomfile, strerror(errno));
+ }
+}
+#endif
+
+static const char *
+gethash(void)
+{
+ const char *hash;
+ struct passwd *pw;
+
+ /* Check if the current user has a password entry */
+ errno = 0;
+ if (!(pw = getpwuid(getuid()))) {
+ if (errno)
+ die("slock: getpwuid: %s\n", strerror(errno));
+ else
+ die("slock: cannot retrieve password entry\n");
+ }
+ hash = pw->pw_passwd;
+
+#if HAVE_SHADOW_H
+ if (!strcmp(hash, "x")) {
+ struct spwd *sp;
+ if (!(sp = getspnam(pw->pw_name)))
+ die("slock: getspnam: cannot retrieve shadow entry. "
+ "Make sure to suid or sgid slock.\n");
+ hash = sp->sp_pwdp;
+ }
+#else
+ if (!strcmp(hash, "*")) {
+#ifdef __OpenBSD__
+ if (!(pw = getpwuid_shadow(getuid())))
+ die("slock: getpwnam_shadow: cannot retrieve shadow entry. "
+ "Make sure to suid or sgid slock.\n");
+ hash = pw->pw_passwd;
+#else
+ die("slock: getpwuid: cannot retrieve shadow entry. "
+ "Make sure to suid or sgid slock.\n");
+#endif /* __OpenBSD__ */
+ }
+#endif /* HAVE_SHADOW_H */
+
+ return hash;
+}
+
+static void
+readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens,
+ const char *hash)
+{
+ XRRScreenChangeNotifyEvent *rre;
+ char buf[32], passwd[256], *inputhash;
+ int num, screen, running, failure, oldc;
+ unsigned int len, color;
+ KeySym ksym;
+ XEvent ev;
+
+ len = 0;
+ running = 1;
+ failure = 0;
+ oldc = INIT;
+
+ while (running && !XNextEvent(dpy, &ev)) {
+ if (ev.type == KeyPress) {
+ explicit_bzero(&buf, sizeof(buf));
+ num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0);
+ if (IsKeypadKey(ksym)) {
+ if (ksym == XK_KP_Enter)
+ ksym = XK_Return;
+ else if (ksym >= XK_KP_0 && ksym <= XK_KP_9)
+ ksym = (ksym - XK_KP_0) + XK_0;
+ }
+ if (IsFunctionKey(ksym) ||
+ IsKeypadKey(ksym) ||
+ IsMiscFunctionKey(ksym) ||
+ IsPFKey(ksym) ||
+ IsPrivateKeypadKey(ksym))
+ continue;
+ switch (ksym) {
+ case XK_Return:
+ passwd[len] = '\0';
+ errno = 0;
+ if (!(inputhash = crypt(passwd, hash)))
+ fprintf(stderr, "slock: crypt: %s\n", strerror(errno));
+ else
+ running = !!strcmp(inputhash, hash);
+ if (running) {
+ XBell(dpy, 100);
+ failure = 1;
+ }
+ explicit_bzero(&passwd, sizeof(passwd));
+ len = 0;
+ break;
+ case XK_Escape:
+ explicit_bzero(&passwd, sizeof(passwd));
+ len = 0;
+ break;
+ case XK_BackSpace:
+ if (len)
+ passwd[--len] = '\0';
+ break;
+ default:
+ if (num && !iscntrl((int)buf[0]) &&
+ (len + num < sizeof(passwd))) {
+ memcpy(passwd + len, buf, num);
+ len += num;
+ }
+ break;
+ }
+ color = len ? INPUT : ((failure || failonclear) ? FAILED : INIT);
+ if (running && oldc != color) {
+ for (screen = 0; screen < nscreens; screen++) {
+ XSetWindowBackground(dpy,
+ locks[screen]->win,
+ locks[screen]->colors[color]);
+ XClearWindow(dpy, locks[screen]->win);
+ }
+ oldc = color;
+ }
+ } else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) {
+ rre = (XRRScreenChangeNotifyEvent*)&ev;
+ for (screen = 0; screen < nscreens; screen++) {
+ if (locks[screen]->win == rre->window) {
+ if (rre->rotation == RR_Rotate_90 ||
+ rre->rotation == RR_Rotate_270)
+ XResizeWindow(dpy, locks[screen]->win,
+ rre->height, rre->width);
+ else
+ XResizeWindow(dpy, locks[screen]->win,
+ rre->width, rre->height);
+ XClearWindow(dpy, locks[screen]->win);
+ break;
+ }
+ }
+ } else {
+ for (screen = 0; screen < nscreens; screen++)
+ XRaiseWindow(dpy, locks[screen]->win);
+ }
+ }
+}
+
+static struct lock *
+lockscreen(Display *dpy, struct xrandr *rr, int screen)
+{
+ char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
+ int i, ptgrab, kbgrab;
+ struct lock *lock;
+ XColor color, dummy;
+ XSetWindowAttributes wa;
+ Cursor invisible;
+
+ if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock))))
+ return NULL;
+
+ lock->screen = screen;
+ lock->root = RootWindow(dpy, lock->screen);
+
+ for (i = 0; i < NUMCOLS; i++) {
+ XAllocNamedColor(dpy, DefaultColormap(dpy, lock->screen),
+ colorname[i], &color, &dummy);
+ lock->colors[i] = color.pixel;
+ }
+
+ /* init */
+ wa.override_redirect = 1;
+ wa.background_pixel = lock->colors[INIT];
+ lock->win = XCreateWindow(dpy, lock->root, 0, 0,
+ DisplayWidth(dpy, lock->screen),
+ DisplayHeight(dpy, lock->screen),
+ 0, DefaultDepth(dpy, lock->screen),
+ CopyFromParent,
+ DefaultVisual(dpy, lock->screen),
+ CWOverrideRedirect | CWBackPixel, &wa);
+ lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8);
+ invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap,
+ &color, &color, 0, 0);
+ XDefineCursor(dpy, lock->win, invisible);
+
+ /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */
+ for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) {
+ if (ptgrab != GrabSuccess) {
+ ptgrab = XGrabPointer(dpy, lock->root, False,
+ ButtonPressMask | ButtonReleaseMask |
+ PointerMotionMask, GrabModeAsync,
+ GrabModeAsync, None, invisible, CurrentTime);
+ }
+ if (kbgrab != GrabSuccess) {
+ kbgrab = XGrabKeyboard(dpy, lock->root, True,
+ GrabModeAsync, GrabModeAsync, CurrentTime);
+ }
+
+ /* input is grabbed: we can lock the screen */
+ if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) {
+ XMapRaised(dpy, lock->win);
+ if (rr->active)
+ XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask);
+
+ XSelectInput(dpy, lock->root, SubstructureNotifyMask);
+ return lock;
+ }
+
+ /* retry on AlreadyGrabbed but fail on other errors */
+ if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) ||
+ (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess))
+ break;
+
+ usleep(100000);
+ }
+
+ /* we couldn't grab all input: fail out */
+ if (ptgrab != GrabSuccess)
+ fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n",
+ screen);
+ if (kbgrab != GrabSuccess)
+ fprintf(stderr, "slock: unable to grab keyboard for screen %d\n",
+ screen);
+ return NULL;
+}
+
+static void
+usage(void)
+{
+ die("usage: slock [-v] [cmd [arg ...]]\n");
+}
+
+int
+main(int argc, char **argv) {
+ struct xrandr rr;
+ struct lock **locks;
+ struct passwd *pwd;
+ struct group *grp;
+ uid_t duid;
+ gid_t dgid;
+ const char *hash;
+ Display *dpy;
+ int s, nlocks, nscreens;
+
+ ARGBEGIN {
+ case 'v':
+ fprintf(stderr, "slock-"VERSION"\n");
+ return 0;
+ default:
+ usage();
+ } ARGEND
+
+ /* validate drop-user and -group */
+ errno = 0;
+ if (!(pwd = getpwnam(user)))
+ die("slock: getpwnam %s: %s\n", user,
+ errno ? strerror(errno) : "user entry not found");
+ duid = pwd->pw_uid;
+ errno = 0;
+ if (!(grp = getgrnam(group)))
+ die("slock: getgrnam %s: %s\n", group,
+ errno ? strerror(errno) : "group entry not found");
+ dgid = grp->gr_gid;
+
+#ifdef __linux__
+ dontkillme();
+#endif
+
+ hash = gethash();
+ errno = 0;
+ if (!crypt("", hash))
+ die("slock: crypt: %s\n", strerror(errno));
+
+ if (!(dpy = XOpenDisplay(NULL)))
+ die("slock: cannot open display\n");
+
+ /* drop privileges */
+ if (setgroups(0, NULL) < 0)
+ die("slock: setgroups: %s\n", strerror(errno));
+ if (setgid(dgid) < 0)
+ die("slock: setgid: %s\n", strerror(errno));
+ if (setuid(duid) < 0)
+ die("slock: setuid: %s\n", strerror(errno));
+
+ /* check for Xrandr support */
+ rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase);
+
+ /* get number of screens in display "dpy" and blank them */
+ nscreens = ScreenCount(dpy);
+ if (!(locks = calloc(nscreens, sizeof(struct lock *))))
+ die("slock: out of memory\n");
+ for (nlocks = 0, s = 0; s < nscreens; s++) {
+ if ((locks[s] = lockscreen(dpy, &rr, s)) != NULL)
+ nlocks++;
+ else
+ break;
+ }
+ XSync(dpy, 0);
+
+ /* did we manage to lock everything? */
+ if (nlocks != nscreens)
+ return 1;
+
+ /* run post-lock command */
+ if (argc > 0) {
+ switch (fork()) {
+ case -1:
+ die("slock: fork failed: %s\n", strerror(errno));
+ case 0:
+ if (close(ConnectionNumber(dpy)) < 0)
+ die("slock: close: %s\n", strerror(errno));
+ execvp(argv[0], argv);
+ fprintf(stderr, "slock: execvp %s: %s\n", argv[0], strerror(errno));
+ _exit(1);
+ }
+ }
+
+ /* everything is now blank. Wait for the correct password */
+ readpw(dpy, &rr, locks, nscreens, hash);
+
+ return 0;
+}
diff --git a/slocko/slock.o b/slocko/slock.o
new file mode 100644
index 0000000..7ef5d46
--- /dev/null
+++ b/slocko/slock.o
Binary files differ
diff --git a/slocko/util.h b/slocko/util.h
new file mode 100644
index 0000000..6f748b8
--- /dev/null
+++ b/slocko/util.h
@@ -0,0 +1,2 @@
+#undef explicit_bzero
+void explicit_bzero(void *, size_t);
diff --git a/slstatuso/LICENSE b/slstatuso/LICENSE
new file mode 100755
index 0000000..70b9fb3
--- /dev/null
+++ b/slstatuso/LICENSE
@@ -0,0 +1,41 @@
+ISC License
+
+Copyright 2016-2022 Aaron Marcher <me@drkhsh.at>
+
+Copyright 2016 Roy Freytag <rfreytag@hs-mittweida.de>
+Copyright 2016 Vincent Loupmon <vincentloupmon@gmail.com>
+Copyright 2016 Daniel Walter <d.walter@0x90.at>
+Copyright 2016-2018 Ali H. Fardan <raiz@firemail.cc>
+Copyright 2016 Jody Leonard <me@jodyleonard.com>
+Copyright 2016-2018 Quentin Rameau <quinq@fifth.space>
+Copyright 2016 Mike Coddington <mike@coddington.us>
+Copyright 2016-2018 parazyd <parazyd@dyne.org>
+Copyright 2017 Tobias Stoeckmann <tobias@stoeckmann.org>
+Copyright 2017-2018 Laslo Hunhold <dev@frign.de>
+Copyright 2018 Darron Anderson <darronanderson@protonmail.com>
+Copyright 2018 Josuah Demangeon <mail@josuah.net>
+Copyright 2018 Tobias Tschinkowitz <tobias@he4d.net>
+Copyright 2018 David Demelier <markand@malikania.fr>
+Copyright 2018-2012 Michael Buch <michaelbuch12@gmail.com>
+Copyright 2018 Ian Remmler <ian@remmler.org>
+Copyright 2016-2019 Joerg Jung <jung@openbsd.org>
+Copyright 2019 Ryan Kes <alrayyes@gmail.com>
+Copyright 2019 Cem Keylan <cem@ckyln.com>
+Copyright 2019 dsp <dsp@2f30.org>
+Copyright 2019-2022 Ingo Feinerer <feinerer@logic.at>
+Copyright 2020 Alexandre Ratchov <alex@caoua.org>
+Copyright 2020 Mart Lubbers <mart@martlubbers.net>
+Copyright 2020 Daniel Moch <daniel@danielmoch.com>
+Copyright 2022 NRK <nrk@disroot.org>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/slstatuso/Makefile b/slstatuso/Makefile
new file mode 100755
index 0000000..7834257
--- /dev/null
+++ b/slstatuso/Makefile
@@ -0,0 +1,68 @@
+# See LICENSE file for copyright and license details
+# slstatus - suckless status monitor
+.POSIX:
+
+include config.mk
+
+REQ = util
+COM =\
+ components/battery\
+ components/cat\
+ components/cpu\
+ components/datetime\
+ components/disk\
+ components/entropy\
+ components/hostname\
+ components/ip\
+ components/kernel_release\
+ components/keyboard_indicators\
+ components/keymap\
+ components/load_avg\
+ components/netspeeds\
+ components/num_files\
+ components/ram\
+ components/run_command\
+ components/swap\
+ components/temperature\
+ components/uptime\
+ components/user\
+ components/volume\
+ components/wifi
+
+all: slstatus
+
+$(COM:=.o): config.mk $(REQ:=.h) slstatus.h
+slstatus.o: slstatus.c slstatus.h arg.h config.h config.mk $(REQ:=.h)
+
+.c.o:
+ $(CC) -o $@ -c $(CPPFLAGS) $(CFLAGS) $<
+
+config.h:
+ cp config.def.h $@
+
+slstatus: slstatus.o $(COM:=.o) $(REQ:=.o)
+ $(CC) -o $@ $(LDFLAGS) $(COM:=.o) $(REQ:=.o) slstatus.o $(LDLIBS)
+
+clean:
+ rm -f slstatus slstatus.o $(COM:=.o) $(REQ:=.o)
+
+dist:
+ rm -rf "slstatus-$(VERSION)"
+ mkdir -p "slstatus-$(VERSION)/components"
+ cp -R LICENSE Makefile README config.mk config.def.h \
+ arg.h slstatus.c $(COM:=.c) $(REQ:=.c) $(REQ:=.h) \
+ slstatus.1 "slstatus-$(VERSION)"
+ tar -cf - "slstatus-$(VERSION)" | gzip -c > "slstatus-$(VERSION).tar.gz"
+ rm -rf "slstatus-$(VERSION)"
+
+install: all
+ mkdir -p "$(DESTDIR)$(PREFIX)/bin"
+ cp -f slstatus "$(DESTDIR)$(PREFIX)/bin"
+ chmod 755 "$(DESTDIR)$(PREFIX)/bin/slstatus"
+ mkdir -p "$(DESTDIR)$(MANPREFIX)/man1"
+ cp -f slstatus.1 "$(DESTDIR)$(MANPREFIX)/man1"
+ chmod 644 "$(DESTDIR)$(MANPREFIX)/man1/slstatus.1"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)/bin/slstatus"
+ rm -f "$(DESTDIR)$(MANPREFIX)/man1/slstatus.1"
diff --git a/slstatuso/README b/slstatuso/README
new file mode 100755
index 0000000..56abfa7
--- /dev/null
+++ b/slstatuso/README
@@ -0,0 +1,71 @@
+slstatuso - suckless status for openbsd
+=======================================
+
+slstatus is a suckless status monitor for window managers that use WM_NAME
+(e.g. dwm) or stdin to fill the status bar.
+
+
+Features
+--------
+- Battery percentage/state/time left
+- Cat (read file)
+- CPU usage
+- CPU frequency
+- Custom shell commands
+- Date and time
+- Disk status (free storage, percentage, total storage and used storage)
+- Available entropy
+- Username/GID/UID
+- Hostname
+- IP address (IPv4 and IPv6)
+- Kernel version
+- Keyboard indicators
+- Keymap
+- Load average
+- Network speeds (RX and TX)
+- Number of files in a directory (hint: Maildir)
+- Memory status (free memory, percentage, total memory and used memory)
+- Swap status (free swap, percentage, total swap and used swap)
+- Temperature
+- Uptime
+- Volume percentage
+- WiFi signal percentage and ESSID
+
+
+Requirements
+------------
+Currently slstatus works on FreeBSD, Linux and OpenBSD.
+In order to build slstatus you need the Xlib header files.
+
+- For volume percentage on Linux the kernel module `snd-mixer-oss` must be
+ loaded.
+- For volume percentage on FreeBSD, `sndio` must be installed.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (slstatus is installed into the
+/usr/local namespace by default).
+
+Afterwards enter the following command to build and install slstatus (if
+necessary as root):
+
+ make clean install
+
+
+Running slstatus
+----------------
+See the man page for details.
+
+
+Configuration
+-------------
+slstatus can be customized by creating a custom config.h and (re)compiling the
+source code. This keeps it fast, secure and simple.
+
+
+Upcoming
+--------
+
+A release (v1.0) will come soon... ;)
+After a long phase of inactivity, development has been continued!
diff --git a/slstatuso/arg.h b/slstatuso/arg.h
new file mode 100755
index 0000000..5f1f408
--- /dev/null
+++ b/slstatuso/arg.h
@@ -0,0 +1,33 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef ARG_H
+#define ARG_H
+
+extern char *argv0;
+
+/* int main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \
+ *argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \
+ int i_, argused_; \
+ if ((*argv)[1] == '-' && !(*argv)[2]) { \
+ argc--, argv++; \
+ break; \
+ } \
+ for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \
+ switch ((*argv)[i_])
+#define ARGEND if (argused_) { \
+ if ((*argv)[i_ + 1]) { \
+ break; \
+ } else { \
+ argc--, argv++; \
+ break; \
+ } \
+ } \
+ } \
+ }
+#define ARGC() ((*argv)[i_])
+#define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \
+ (*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x))
+#define EARGF(x) ARGF_(((x), exit(1), (char *)0))
+#define ARGF() ARGF_((char *)0)
+
+#endif
diff --git a/slstatuso/components/battery.c b/slstatuso/components/battery.c
new file mode 100755
index 0000000..9fa1014
--- /dev/null
+++ b/slstatuso/components/battery.c
@@ -0,0 +1,244 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(__linux__)
+ #include <limits.h>
+ #include <stdint.h>
+ #include <unistd.h>
+
+ #define POWER_SUPPLY_CAPACITY "/sys/class/power_supply/%s/capacity"
+ #define POWER_SUPPLY_STATUS "/sys/class/power_supply/%s/status"
+ #define POWER_SUPPLY_CHARGE "/sys/class/power_supply/%s/charge_now"
+ #define POWER_SUPPLY_ENERGY "/sys/class/power_supply/%s/energy_now"
+ #define POWER_SUPPLY_CURRENT "/sys/class/power_supply/%s/current_now"
+ #define POWER_SUPPLY_POWER "/sys/class/power_supply/%s/power_now"
+
+ static const char *
+ pick(const char *bat, const char *f1, const char *f2, char *path,
+ size_t length)
+ {
+ if (esnprintf(path, length, f1, bat) > 0 &&
+ access(path, R_OK) == 0)
+ return f1;
+
+ if (esnprintf(path, length, f2, bat) > 0 &&
+ access(path, R_OK) == 0)
+ return f2;
+
+ return NULL;
+ }
+
+ const char *
+ battery_perc(const char *bat)
+ {
+ int perc;
+ char path[PATH_MAX];
+
+ if (esnprintf(path, sizeof(path), POWER_SUPPLY_CAPACITY, bat) < 0)
+ return NULL;
+ if (pscanf(path, "%d", &perc) != 1)
+ return NULL;
+
+ return bprintf("%d", perc);
+ }
+
+ const char *
+ battery_state(const char *bat)
+ {
+ static struct {
+ char *state;
+ char *symbol;
+ } map[] = {
+ { "Charging", "+" },
+ { "Discharging", "-" },
+ { "Full", "o" },
+ { "Not charging", "o" },
+ };
+ size_t i;
+ char path[PATH_MAX], state[12];
+
+ if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0)
+ return NULL;
+ if (pscanf(path, "%12[a-zA-Z ]", state) != 1)
+ return NULL;
+
+ for (i = 0; i < LEN(map); i++)
+ if (!strcmp(map[i].state, state))
+ break;
+
+ return (i == LEN(map)) ? "?" : map[i].symbol;
+ }
+
+ const char *
+ battery_remaining(const char *bat)
+ {
+ uintmax_t charge_now, current_now, m, h;
+ double timeleft;
+ char path[PATH_MAX], state[12];
+
+ if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0)
+ return NULL;
+ if (pscanf(path, "%12[a-zA-Z ]", state) != 1)
+ return NULL;
+
+ if (!pick(bat, POWER_SUPPLY_CHARGE, POWER_SUPPLY_ENERGY, path,
+ sizeof(path)) ||
+ pscanf(path, "%ju", &charge_now) < 0)
+ return NULL;
+
+ if (!strcmp(state, "Discharging")) {
+ if (!pick(bat, POWER_SUPPLY_CURRENT, POWER_SUPPLY_POWER, path,
+ sizeof(path)) ||
+ pscanf(path, "%ju", &current_now) < 0)
+ return NULL;
+
+ if (current_now == 0)
+ return NULL;
+
+ timeleft = (double)charge_now / (double)current_now;
+ h = timeleft;
+ m = (timeleft - (double)h) * 60;
+
+ return bprintf("%juh %jum", h, m);
+ }
+
+ return "";
+ }
+#elif defined(__OpenBSD__)
+ #include <fcntl.h>
+ #include <machine/apmvar.h>
+ #include <sys/ioctl.h>
+ #include <unistd.h>
+
+ static int
+ load_apm_power_info(struct apm_power_info *apm_info)
+ {
+ int fd;
+
+ fd = open("/dev/apm", O_RDONLY);
+ if (fd < 0) {
+ warn("open '/dev/apm':");
+ return 0;
+ }
+
+ memset(apm_info, 0, sizeof(struct apm_power_info));
+ if (ioctl(fd, APM_IOC_GETPOWER, apm_info) < 0) {
+ warn("ioctl 'APM_IOC_GETPOWER':");
+ close(fd);
+ return 0;
+ }
+ return close(fd), 1;
+ }
+
+ const char *
+ battery_perc(const char *unused)
+ {
+ struct apm_power_info apm_info;
+
+ if (load_apm_power_info(&apm_info))
+ return bprintf("%d", apm_info.battery_life);
+
+ return NULL;
+ }
+
+ const char *
+ battery_state(const char *unused)
+ {
+ struct {
+ unsigned int state;
+ char *symbol;
+ } map[] = {
+ { APM_AC_ON, "+" },
+ { APM_AC_OFF, "-" },
+ };
+ struct apm_power_info apm_info;
+ size_t i;
+
+ if (load_apm_power_info(&apm_info)) {
+ for (i = 0; i < LEN(map); i++)
+ if (map[i].state == apm_info.ac_state)
+ break;
+
+ return (i == LEN(map)) ? "?" : map[i].symbol;
+ }
+
+ return NULL;
+ }
+
+ const char *
+ battery_remaining(const char *unused)
+ {
+ struct apm_power_info apm_info;
+ unsigned int h, m;
+
+ if (load_apm_power_info(&apm_info)) {
+ if (apm_info.ac_state != APM_AC_ON) {
+ h = apm_info.minutes_left / 60;
+ m = apm_info.minutes_left % 60;
+ return bprintf("%uh %02um", h, m);
+ } else {
+ return "";
+ }
+ }
+
+ return NULL;
+ }
+#elif defined(__FreeBSD__)
+ #include <sys/sysctl.h>
+
+ #define BATTERY_LIFE "hw.acpi.battery.life"
+ #define BATTERY_STATE "hw.acpi.battery.state"
+ #define BATTERY_TIME "hw.acpi.battery.time"
+
+ const char *
+ battery_perc(const char *unused)
+ {
+ int cap;
+ size_t len;
+
+ len = sizeof(cap);
+ if (sysctlbyname(BATTERY_LIFE, &cap, &len, NULL, 0) < 0 || !len)
+ return NULL;
+
+ return bprintf("%d", cap);
+ }
+
+ const char *
+ battery_state(const char *unused)
+ {
+ int state;
+ size_t len;
+
+ len = sizeof(state);
+ if (sysctlbyname(BATTERY_STATE, &state, &len, NULL, 0) < 0 || !len)
+ return NULL;
+
+ switch (state) {
+ case 0: /* FALLTHROUGH */
+ case 2:
+ return "+";
+ case 1:
+ return "-";
+ default:
+ return "?";
+ }
+ }
+
+ const char *
+ battery_remaining(const char *unused)
+ {
+ int rem;
+ size_t len;
+
+ len = sizeof(rem);
+ if (sysctlbyname(BATTERY_TIME, &rem, &len, NULL, 0) < 0 || !len
+ || rem < 0)
+ return NULL;
+
+ return bprintf("%uh %02um", rem / 60, rem % 60);
+ }
+#endif
diff --git a/slstatuso/components/battery.o b/slstatuso/components/battery.o
new file mode 100644
index 0000000..3399bea
--- /dev/null
+++ b/slstatuso/components/battery.o
Binary files differ
diff --git a/slstatuso/components/cat.c b/slstatuso/components/cat.c
new file mode 100755
index 0000000..07944cc
--- /dev/null
+++ b/slstatuso/components/cat.c
@@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+cat(const char *path)
+{
+ char *f;
+ FILE *fp;
+
+ if (!(fp = fopen(path, "r"))) {
+ warn("fopen '%s':", path);
+ return NULL;
+ }
+
+ f = fgets(buf, sizeof(buf) - 1, fp);
+ if (fclose(fp) < 0) {
+ warn("fclose '%s':", path);
+ return NULL;
+ }
+ if (!f)
+ return NULL;
+
+ if ((f = strrchr(buf, '\n')))
+ f[0] = '\0';
+
+ return buf[0] ? buf : NULL;
+}
+
diff --git a/slstatuso/components/cat.o b/slstatuso/components/cat.o
new file mode 100644
index 0000000..2ac3267
--- /dev/null
+++ b/slstatuso/components/cat.o
Binary files differ
diff --git a/slstatuso/components/cpu.c b/slstatuso/components/cpu.c
new file mode 100755
index 0000000..d0d03c7
--- /dev/null
+++ b/slstatuso/components/cpu.c
@@ -0,0 +1,157 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(__linux__)
+ #define CPU_FREQ "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"
+
+ const char *
+ cpu_freq(const char *unused)
+ {
+ uintmax_t freq;
+
+ /* in kHz */
+ if (pscanf(CPU_FREQ, "%ju", &freq) != 1)
+ return NULL;
+
+ return fmt_human(freq * 1000, 1000);
+ }
+
+ const char *
+ cpu_perc(const char *unused)
+ {
+ static long double a[7];
+ long double b[7], sum;
+
+ memcpy(b, a, sizeof(b));
+ /* cpu user nice system idle iowait irq softirq */
+ if (pscanf("/proc/stat", "%*s %Lf %Lf %Lf %Lf %Lf %Lf %Lf",
+ &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6])
+ != 7)
+ return NULL;
+
+ if (b[0] == 0)
+ return NULL;
+
+ sum = (b[0] + b[1] + b[2] + b[3] + b[4] + b[5] + b[6]) -
+ (a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6]);
+
+ if (sum == 0)
+ return NULL;
+
+ return bprintf("%d", (int)(100 *
+ ((b[0] + b[1] + b[2] + b[5] + b[6]) -
+ (a[0] + a[1] + a[2] + a[5] + a[6])) / sum));
+ }
+#elif defined(__OpenBSD__)
+ #include <sys/param.h>
+ #include <sys/sched.h>
+ #include <sys/sysctl.h>
+
+ const char *
+ cpu_freq(const char *unused)
+ {
+ int freq, mib[2];
+ size_t size;
+
+ mib[0] = CTL_HW;
+ mib[1] = HW_CPUSPEED;
+
+ size = sizeof(freq);
+
+ /* in MHz */
+ if (sysctl(mib, 2, &freq, &size, NULL, 0) < 0) {
+ warn("sysctl 'HW_CPUSPEED':");
+ return NULL;
+ }
+
+ return fmt_human(freq * 1E6, 1000);
+ }
+
+ const char *
+ cpu_perc(const char *unused)
+ {
+ int mib[2];
+ static uintmax_t a[CPUSTATES];
+ uintmax_t b[CPUSTATES], sum;
+ size_t size;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_CPTIME;
+
+ size = sizeof(a);
+
+ memcpy(b, a, sizeof(b));
+ if (sysctl(mib, 2, &a, &size, NULL, 0) < 0) {
+ warn("sysctl 'KERN_CPTIME':");
+ return NULL;
+ }
+ if (b[0] == 0)
+ return NULL;
+
+ sum = (a[CP_USER] + a[CP_NICE] + a[CP_SYS] + a[CP_INTR] + a[CP_IDLE]) -
+ (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + b[CP_INTR] + b[CP_IDLE]);
+
+ if (sum == 0)
+ return NULL;
+
+ return bprintf("%d", 100 *
+ ((a[CP_USER] + a[CP_NICE] + a[CP_SYS] +
+ a[CP_INTR]) -
+ (b[CP_USER] + b[CP_NICE] + b[CP_SYS] +
+ b[CP_INTR])) / sum);
+ }
+#elif defined(__FreeBSD__)
+ #include <devstat.h>
+ #include <sys/param.h>
+ #include <sys/sysctl.h>
+
+ const char *
+ cpu_freq(const char *unused)
+ {
+ int freq;
+ size_t size;
+
+ size = sizeof(freq);
+ /* in MHz */
+ if (sysctlbyname("hw.clockrate", &freq, &size, NULL, 0) < 0 || !size) {
+ warn("sysctlbyname 'hw.clockrate':");
+ return NULL;
+ }
+
+ return fmt_human(freq * 1E6, 1000);
+ }
+
+ const char *
+ cpu_perc(const char *unused)
+ {
+ size_t size;
+ static long a[CPUSTATES];
+ long b[CPUSTATES], sum;
+
+ size = sizeof(a);
+ memcpy(b, a, sizeof(b));
+ if (sysctlbyname("kern.cp_time", &a, &size, NULL, 0) < 0 || !size) {
+ warn("sysctlbyname 'kern.cp_time':");
+ return NULL;
+ }
+ if (b[0] == 0)
+ return NULL;
+
+ sum = (a[CP_USER] + a[CP_NICE] + a[CP_SYS] + a[CP_INTR] + a[CP_IDLE]) -
+ (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + b[CP_INTR] + b[CP_IDLE]);
+
+ if (sum == 0)
+ return NULL;
+
+ return bprintf("%d", 100 *
+ ((a[CP_USER] + a[CP_NICE] + a[CP_SYS] +
+ a[CP_INTR]) -
+ (b[CP_USER] + b[CP_NICE] + b[CP_SYS] +
+ b[CP_INTR])) / sum);
+ }
+#endif
diff --git a/slstatuso/components/cpu.o b/slstatuso/components/cpu.o
new file mode 100644
index 0000000..666515a
--- /dev/null
+++ b/slstatuso/components/cpu.o
Binary files differ
diff --git a/slstatuso/components/datetime.c b/slstatuso/components/datetime.c
new file mode 100755
index 0000000..5b10daf
--- /dev/null
+++ b/slstatuso/components/datetime.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <time.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+datetime(const char *fmt)
+{
+ time_t t;
+
+ t = time(NULL);
+ if (!strftime(buf, sizeof(buf), fmt, localtime(&t))) {
+ warn("strftime: Result string exceeds buffer size");
+ return NULL;
+ }
+
+ return buf;
+}
diff --git a/slstatuso/components/datetime.o b/slstatuso/components/datetime.o
new file mode 100644
index 0000000..0bfe5c1
--- /dev/null
+++ b/slstatuso/components/datetime.o
Binary files differ
diff --git a/slstatuso/components/disk.c b/slstatuso/components/disk.c
new file mode 100755
index 0000000..0d1c13e
--- /dev/null
+++ b/slstatuso/components/disk.c
@@ -0,0 +1,59 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <sys/statvfs.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+disk_free(const char *path)
+{
+ struct statvfs fs;
+
+ if (statvfs(path, &fs) < 0) {
+ warn("statvfs '%s':", path);
+ return NULL;
+ }
+
+ return fmt_human(fs.f_frsize * fs.f_bavail, 1024);
+}
+
+const char *
+disk_perc(const char *path)
+{
+ struct statvfs fs;
+
+ if (statvfs(path, &fs) < 0) {
+ warn("statvfs '%s':", path);
+ return NULL;
+ }
+
+ return bprintf("%d", (int)(100 *
+ (1.0f - ((float)fs.f_bavail / (float)fs.f_blocks))));
+}
+
+const char *
+disk_total(const char *path)
+{
+ struct statvfs fs;
+
+ if (statvfs(path, &fs) < 0) {
+ warn("statvfs '%s':", path);
+ return NULL;
+ }
+
+ return fmt_human(fs.f_frsize * fs.f_blocks, 1024);
+}
+
+const char *
+disk_used(const char *path)
+{
+ struct statvfs fs;
+
+ if (statvfs(path, &fs) < 0) {
+ warn("statvfs '%s':", path);
+ return NULL;
+ }
+
+ return fmt_human(fs.f_frsize * (fs.f_blocks - fs.f_bfree), 1024);
+}
diff --git a/slstatuso/components/disk.o b/slstatuso/components/disk.o
new file mode 100644
index 0000000..551c092
--- /dev/null
+++ b/slstatuso/components/disk.o
Binary files differ
diff --git a/slstatuso/components/entropy.c b/slstatuso/components/entropy.c
new file mode 100755
index 0000000..66b2e5a
--- /dev/null
+++ b/slstatuso/components/entropy.c
@@ -0,0 +1,28 @@
+/* See LICENSE file for copyright and license details. */
+#include "../slstatus.h"
+#if defined(__linux__)
+ #include <stdint.h>
+ #include <stdio.h>
+
+ #include "../util.h"
+
+ #define ENTROPY_AVAIL "/proc/sys/kernel/random/entropy_avail"
+
+ const char *
+ entropy(const char *unused)
+ {
+ uintmax_t num;
+
+ if (pscanf(ENTROPY_AVAIL, "%ju", &num) != 1)
+ return NULL;
+
+ return bprintf("%ju", num);
+ }
+#elif defined(__OpenBSD__) | defined(__FreeBSD__)
+ const char *
+ entropy(const char *unused)
+ {
+ /* Unicode Character 'INFINITY' (U+221E) */
+ return "\xe2\x88\x9e";
+ }
+#endif
diff --git a/slstatuso/components/entropy.o b/slstatuso/components/entropy.o
new file mode 100644
index 0000000..f39d947
--- /dev/null
+++ b/slstatuso/components/entropy.o
Binary files differ
diff --git a/slstatuso/components/hostname.c b/slstatuso/components/hostname.c
new file mode 100755
index 0000000..dab8b63
--- /dev/null
+++ b/slstatuso/components/hostname.c
@@ -0,0 +1,17 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <unistd.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+hostname(const char *unused)
+{
+ if (gethostname(buf, sizeof(buf)) < 0) {
+ warn("gethostbyname:");
+ return NULL;
+ }
+
+ return buf;
+}
diff --git a/slstatuso/components/hostname.o b/slstatuso/components/hostname.o
new file mode 100644
index 0000000..974ed3a
--- /dev/null
+++ b/slstatuso/components/hostname.o
Binary files differ
diff --git a/slstatuso/components/ip.c b/slstatuso/components/ip.c
new file mode 100755
index 0000000..9476549
--- /dev/null
+++ b/slstatuso/components/ip.c
@@ -0,0 +1,61 @@
+/* See LICENSE file for copyright and license details. */
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#if defined(__OpenBSD__)
+ #include <sys/socket.h>
+ #include <sys/types.h>
+#elif defined(__FreeBSD__)
+ #include <netinet/in.h>
+ #include <sys/socket.h>
+#endif
+
+#include "../slstatus.h"
+#include "../util.h"
+
+static const char *
+ip(const char *interface, unsigned short sa_family)
+{
+ struct ifaddrs *ifaddr, *ifa;
+ int s;
+ char host[NI_MAXHOST];
+
+ if (getifaddrs(&ifaddr) < 0) {
+ warn("getifaddrs:");
+ return NULL;
+ }
+
+ for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+ if (!ifa->ifa_addr)
+ continue;
+
+ s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+ if (!strcmp(ifa->ifa_name, interface) &&
+ (ifa->ifa_addr->sa_family == sa_family)) {
+ freeifaddrs(ifaddr);
+ if (s != 0) {
+ warn("getnameinfo: %s", gai_strerror(s));
+ return NULL;
+ }
+ return bprintf("%s", host);
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ return NULL;
+}
+
+const char *
+ipv4(const char *interface)
+{
+ return ip(interface, AF_INET);
+}
+
+const char *
+ipv6(const char *interface)
+{
+ return ip(interface, AF_INET6);
+}
diff --git a/slstatuso/components/ip.o b/slstatuso/components/ip.o
new file mode 100644
index 0000000..f3f98c4
--- /dev/null
+++ b/slstatuso/components/ip.o
Binary files differ
diff --git a/slstatuso/components/kernel_release.c b/slstatuso/components/kernel_release.c
new file mode 100755
index 0000000..36a6a44
--- /dev/null
+++ b/slstatuso/components/kernel_release.c
@@ -0,0 +1,19 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <sys/utsname.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+kernel_release(const char *unused)
+{
+ struct utsname udata;
+
+ if (uname(&udata) < 0) {
+ warn("uname:");
+ return NULL;
+ }
+
+ return bprintf("%s", udata.release);
+}
diff --git a/slstatuso/components/kernel_release.o b/slstatuso/components/kernel_release.o
new file mode 100644
index 0000000..262be34
--- /dev/null
+++ b/slstatuso/components/kernel_release.o
Binary files differ
diff --git a/slstatuso/components/keyboard_indicators.c b/slstatuso/components/keyboard_indicators.c
new file mode 100755
index 0000000..5f62bb7
--- /dev/null
+++ b/slstatuso/components/keyboard_indicators.c
@@ -0,0 +1,50 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <X11/Xlib.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+/*
+ * fmt consists of uppercase or lowercase 'c' for caps lock and/or 'n' for num
+ * lock, each optionally followed by '?', in the order of indicators desired.
+ * If followed by '?', the letter with case preserved is included in the output
+ * if the corresponding indicator is on. Otherwise, the letter is always
+ * included, lowercase when off and uppercase when on.
+ */
+const char *
+keyboard_indicators(const char *fmt)
+{
+ Display *dpy;
+ XKeyboardState state;
+ size_t fmtlen, i, n;
+ int togglecase, isset;
+ char key;
+
+ if (!(dpy = XOpenDisplay(NULL))) {
+ warn("XOpenDisplay: Failed to open display");
+ return NULL;
+ }
+ XGetKeyboardControl(dpy, &state);
+ XCloseDisplay(dpy);
+
+ fmtlen = strnlen(fmt, 4);
+ for (i = n = 0; i < fmtlen; i++) {
+ key = tolower(fmt[i]);
+ if (key != 'c' && key != 'n')
+ continue;
+
+ togglecase = (i + 1 >= fmtlen || fmt[i + 1] != '?');
+ isset = (state.led_mask & (1 << (key == 'n')));
+
+ if (togglecase)
+ buf[n++] = isset ? toupper(key) : key;
+ else if (isset)
+ buf[n++] = fmt[i];
+ }
+
+ buf[n] = 0;
+ return buf;
+}
diff --git a/slstatuso/components/keyboard_indicators.o b/slstatuso/components/keyboard_indicators.o
new file mode 100644
index 0000000..537f678
--- /dev/null
+++ b/slstatuso/components/keyboard_indicators.o
Binary files differ
diff --git a/slstatuso/components/keymap.c b/slstatuso/components/keymap.c
new file mode 100755
index 0000000..4740431
--- /dev/null
+++ b/slstatuso/components/keymap.c
@@ -0,0 +1,85 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/XKBlib.h>
+#include <X11/Xlib.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+static int
+valid_layout_or_variant(char *sym)
+{
+ size_t i;
+ /* invalid symbols from xkb rules config */
+ static const char *invalid[] = { "evdev", "inet", "pc", "base" };
+
+ for (i = 0; i < LEN(invalid); i++)
+ if (!strncmp(sym, invalid[i], strlen(invalid[i])))
+ return 0;
+
+ return 1;
+}
+
+static char *
+get_layout(char *syms, int grp_num)
+{
+ char *tok, *layout;
+ int grp;
+
+ layout = NULL;
+ tok = strtok(syms, "+:");
+ for (grp = 0; tok && grp <= grp_num; tok = strtok(NULL, "+:")) {
+ if (!valid_layout_or_variant(tok)) {
+ continue;
+ } else if (strlen(tok) == 1 && isdigit(tok[0])) {
+ /* ignore :2, :3, :4 (additional layout groups) */
+ continue;
+ }
+ layout = tok;
+ grp++;
+ }
+
+ return layout;
+}
+
+const char *
+keymap(const char *unused)
+{
+ Display *dpy;
+ XkbDescRec *desc;
+ XkbStateRec state;
+ char *symbols, *layout;
+
+ layout = NULL;
+
+ if (!(dpy = XOpenDisplay(NULL))) {
+ warn("XOpenDisplay: Failed to open display");
+ return NULL;
+ }
+ if (!(desc = XkbAllocKeyboard())) {
+ warn("XkbAllocKeyboard: Failed to allocate keyboard");
+ goto end;
+ }
+ if (XkbGetNames(dpy, XkbSymbolsNameMask, desc)) {
+ warn("XkbGetNames: Failed to retrieve key symbols");
+ goto end;
+ }
+ if (XkbGetState(dpy, XkbUseCoreKbd, &state)) {
+ warn("XkbGetState: Failed to retrieve keyboard state");
+ goto end;
+ }
+ if (!(symbols = XGetAtomName(dpy, desc->names->symbols))) {
+ warn("XGetAtomName: Failed to get atom name");
+ goto end;
+ }
+ layout = (char *)bprintf("%s", get_layout(symbols, state.group));
+ XFree(symbols);
+end:
+ XkbFreeKeyboard(desc, XkbSymbolsNameMask, 1);
+ if (XCloseDisplay(dpy))
+ warn("XCloseDisplay: Failed to close display");
+
+ return layout;
+}
diff --git a/slstatuso/components/keymap.o b/slstatuso/components/keymap.o
new file mode 100644
index 0000000..f8feba8
--- /dev/null
+++ b/slstatuso/components/keymap.o
Binary files differ
diff --git a/slstatuso/components/load_avg.c b/slstatuso/components/load_avg.c
new file mode 100755
index 0000000..f278a40
--- /dev/null
+++ b/slstatuso/components/load_avg.c
@@ -0,0 +1,19 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+load_avg(const char *unused)
+{
+ double avgs[3];
+
+ if (getloadavg(avgs, 3) < 0) {
+ warn("getloadavg: Failed to obtain load average");
+ return NULL;
+ }
+
+ return bprintf("%.2f %.2f %.2f", avgs[0], avgs[1], avgs[2]);
+}
diff --git a/slstatuso/components/load_avg.o b/slstatuso/components/load_avg.o
new file mode 100644
index 0000000..474f6ca
--- /dev/null
+++ b/slstatuso/components/load_avg.o
Binary files differ
diff --git a/slstatuso/components/netspeeds.c b/slstatuso/components/netspeeds.c
new file mode 100755
index 0000000..cde6fa9
--- /dev/null
+++ b/slstatuso/components/netspeeds.c
@@ -0,0 +1,129 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdio.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(__linux__)
+ #include <stdint.h>
+
+ #define NET_RX_BYTES "/sys/class/net/%s/statistics/rx_bytes"
+ #define NET_TX_BYTES "/sys/class/net/%s/statistics/tx_bytes"
+
+ const char *
+ netspeed_rx(const char *interface)
+ {
+ uintmax_t oldrxbytes;
+ static uintmax_t rxbytes;
+ extern const unsigned int interval;
+ char path[PATH_MAX];
+
+ oldrxbytes = rxbytes;
+
+ if (esnprintf(path, sizeof(path), NET_RX_BYTES, interface) < 0)
+ return NULL;
+ if (pscanf(path, "%ju", &rxbytes) != 1)
+ return NULL;
+ if (oldrxbytes == 0)
+ return NULL;
+
+ return fmt_human((rxbytes - oldrxbytes) * 1000 / interval,
+ 1024);
+ }
+
+ const char *
+ netspeed_tx(const char *interface)
+ {
+ uintmax_t oldtxbytes;
+ static uintmax_t txbytes;
+ extern const unsigned int interval;
+ char path[PATH_MAX];
+
+ oldtxbytes = txbytes;
+
+ if (esnprintf(path, sizeof(path), NET_TX_BYTES, interface) < 0)
+ return NULL;
+ if (pscanf(path, "%ju", &txbytes) != 1)
+ return NULL;
+ if (oldtxbytes == 0)
+ return NULL;
+
+ return fmt_human((txbytes - oldtxbytes) * 1000 / interval,
+ 1024);
+ }
+#elif defined(__OpenBSD__) | defined(__FreeBSD__)
+ #include <ifaddrs.h>
+ #include <net/if.h>
+ #include <string.h>
+ #include <sys/types.h>
+ #include <sys/socket.h>
+
+ const char *
+ netspeed_rx(const char *interface)
+ {
+ struct ifaddrs *ifal, *ifa;
+ struct if_data *ifd;
+ uintmax_t oldrxbytes;
+ static uintmax_t rxbytes;
+ extern const unsigned int interval;
+ int if_ok = 0;
+
+ oldrxbytes = rxbytes;
+
+ if (getifaddrs(&ifal) < 0) {
+ warn("getifaddrs failed");
+ return NULL;
+ }
+ rxbytes = 0;
+ for (ifa = ifal; ifa; ifa = ifa->ifa_next)
+ if (!strcmp(ifa->ifa_name, interface) &&
+ (ifd = (struct if_data *)ifa->ifa_data))
+ rxbytes += ifd->ifi_ibytes, if_ok = 1;
+
+ freeifaddrs(ifal);
+ if (!if_ok) {
+ warn("reading 'if_data' failed");
+ return NULL;
+ }
+ if (oldrxbytes == 0)
+ return NULL;
+
+ return fmt_human((rxbytes - oldrxbytes) * 1000 / interval,
+ 1024);
+ }
+
+ const char *
+ netspeed_tx(const char *interface)
+ {
+ struct ifaddrs *ifal, *ifa;
+ struct if_data *ifd;
+ uintmax_t oldtxbytes;
+ static uintmax_t txbytes;
+ extern const unsigned int interval;
+ int if_ok = 0;
+
+ oldtxbytes = txbytes;
+
+ if (getifaddrs(&ifal) < 0) {
+ warn("getifaddrs failed");
+ return NULL;
+ }
+ txbytes = 0;
+ for (ifa = ifal; ifa; ifa = ifa->ifa_next)
+ if (!strcmp(ifa->ifa_name, interface) &&
+ (ifd = (struct if_data *)ifa->ifa_data))
+ txbytes += ifd->ifi_obytes, if_ok = 1;
+
+ freeifaddrs(ifal);
+ if (!if_ok) {
+ warn("reading 'if_data' failed");
+ return NULL;
+ }
+ if (oldtxbytes == 0)
+ return NULL;
+
+ return fmt_human((txbytes - oldtxbytes) * 1000 / interval,
+ 1024);
+ }
+#endif
diff --git a/slstatuso/components/netspeeds.o b/slstatuso/components/netspeeds.o
new file mode 100644
index 0000000..ee55be9
--- /dev/null
+++ b/slstatuso/components/netspeeds.o
Binary files differ
diff --git a/slstatuso/components/num_files.c b/slstatuso/components/num_files.c
new file mode 100755
index 0000000..e4b4281
--- /dev/null
+++ b/slstatuso/components/num_files.c
@@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#include <dirent.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+num_files(const char *path)
+{
+ struct dirent *dp;
+ DIR *fd;
+ int num;
+
+ if (!(fd = opendir(path))) {
+ warn("opendir '%s':", path);
+ return NULL;
+ }
+
+ num = 0;
+ while ((dp = readdir(fd))) {
+ if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+ continue; /* skip self and parent */
+
+ num++;
+ }
+
+ closedir(fd);
+
+ return bprintf("%d", num);
+}
diff --git a/slstatuso/components/num_files.o b/slstatuso/components/num_files.o
new file mode 100644
index 0000000..7e7e667
--- /dev/null
+++ b/slstatuso/components/num_files.o
Binary files differ
diff --git a/slstatuso/components/ram.c b/slstatuso/components/ram.c
new file mode 100755
index 0000000..15c4b74
--- /dev/null
+++ b/slstatuso/components/ram.c
@@ -0,0 +1,212 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(__linux__)
+ #include <stdint.h>
+
+ const char *
+ ram_free(const char *unused)
+ {
+ uintmax_t free;
+
+ if (pscanf("/proc/meminfo",
+ "MemTotal: %ju kB\n"
+ "MemFree: %ju kB\n"
+ "MemAvailable: %ju kB\n",
+ &free, &free, &free) != 3)
+ return NULL;
+
+ return fmt_human(free * 1024, 1024);
+ }
+
+ const char *
+ ram_perc(const char *unused)
+ {
+ uintmax_t total, free, buffers, cached;
+ int percent;
+
+ if (pscanf("/proc/meminfo",
+ "MemTotal: %ju kB\n"
+ "MemFree: %ju kB\n"
+ "MemAvailable: %ju kB\n"
+ "Buffers: %ju kB\n"
+ "Cached: %ju kB\n",
+ &total, &free, &buffers, &buffers, &cached) != 5)
+ return NULL;
+
+ if (total == 0)
+ return NULL;
+
+ percent = 100 * ((total - free) - (buffers + cached)) / total;
+ return bprintf("%d", percent);
+ }
+
+ const char *
+ ram_total(const char *unused)
+ {
+ uintmax_t total;
+
+ if (pscanf("/proc/meminfo", "MemTotal: %ju kB\n", &total)
+ != 1)
+ return NULL;
+
+ return fmt_human(total * 1024, 1024);
+ }
+
+ const char *
+ ram_used(const char *unused)
+ {
+ uintmax_t total, free, buffers, cached, used;
+
+ if (pscanf("/proc/meminfo",
+ "MemTotal: %ju kB\n"
+ "MemFree: %ju kB\n"
+ "MemAvailable: %ju kB\n"
+ "Buffers: %ju kB\n"
+ "Cached: %ju kB\n",
+ &total, &free, &buffers, &buffers, &cached) != 5)
+ return NULL;
+
+ used = (total - free - buffers - cached);
+ return fmt_human(used * 1024, 1024);
+ }
+#elif defined(__OpenBSD__)
+ #include <stdlib.h>
+ #include <sys/sysctl.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+
+ #define LOG1024 10
+ #define pagetok(size, pageshift) (size_t)(size << (pageshift - LOG1024))
+
+ inline int
+ load_uvmexp(struct uvmexp *uvmexp)
+ {
+ int uvmexp_mib[] = {CTL_VM, VM_UVMEXP};
+ size_t size;
+
+ size = sizeof(*uvmexp);
+
+ if (sysctl(uvmexp_mib, 2, uvmexp, &size, NULL, 0) >= 0)
+ return 1;
+
+ return 0;
+ }
+
+ const char *
+ ram_free(const char *unused)
+ {
+ struct uvmexp uvmexp;
+ int free_pages;
+
+ if (!load_uvmexp(&uvmexp))
+ return NULL;
+
+ free_pages = uvmexp.npages - uvmexp.active;
+ return fmt_human(pagetok(free_pages, uvmexp.pageshift) *
+ 1024, 1024);
+ }
+
+ const char *
+ ram_perc(const char *unused)
+ {
+ struct uvmexp uvmexp;
+ int percent;
+
+ if (!load_uvmexp(&uvmexp))
+ return NULL;
+
+ percent = uvmexp.active * 100 / uvmexp.npages;
+ return bprintf("%d", percent);
+ }
+
+ const char *
+ ram_total(const char *unused)
+ {
+ struct uvmexp uvmexp;
+
+ if (!load_uvmexp(&uvmexp))
+ return NULL;
+
+ return fmt_human(pagetok(uvmexp.npages,
+ uvmexp.pageshift) * 1024, 1024);
+ }
+
+ const char *
+ ram_used(const char *unused)
+ {
+ struct uvmexp uvmexp;
+
+ if (!load_uvmexp(&uvmexp))
+ return NULL;
+
+ return fmt_human(pagetok(uvmexp.active,
+ uvmexp.pageshift) * 1024, 1024);
+ }
+#elif defined(__FreeBSD__)
+ #include <sys/sysctl.h>
+ #include <sys/vmmeter.h>
+ #include <unistd.h>
+ #include <vm/vm_param.h>
+
+ const char *
+ ram_free(const char *unused) {
+ struct vmtotal vm_stats;
+ int mib[] = {CTL_VM, VM_TOTAL};
+ size_t len;
+
+ len = sizeof(struct vmtotal);
+ if (sysctl(mib, 2, &vm_stats, &len, NULL, 0) < 0
+ || !len)
+ return NULL;
+
+ return fmt_human(vm_stats.t_free * getpagesize(), 1024);
+ }
+
+ const char *
+ ram_total(const char *unused) {
+ unsigned int npages;
+ size_t len;
+
+ len = sizeof(npages);
+ if (sysctlbyname("vm.stats.vm.v_page_count",
+ &npages, &len, NULL, 0) < 0 || !len)
+ return NULL;
+
+ return fmt_human(npages * getpagesize(), 1024);
+ }
+
+ const char *
+ ram_perc(const char *unused) {
+ unsigned int npages;
+ unsigned int active;
+ size_t len;
+
+ len = sizeof(npages);
+ if (sysctlbyname("vm.stats.vm.v_page_count",
+ &npages, &len, NULL, 0) < 0 || !len)
+ return NULL;
+
+ if (sysctlbyname("vm.stats.vm.v_active_count",
+ &active, &len, NULL, 0) < 0 || !len)
+ return NULL;
+
+ return bprintf("%d", active * 100 / npages);
+ }
+
+ const char *
+ ram_used(const char *unused) {
+ unsigned int active;
+ size_t len;
+
+ len = sizeof(active);
+ if (sysctlbyname("vm.stats.vm.v_active_count",
+ &active, &len, NULL, 0) < 0 || !len)
+ return NULL;
+
+ return fmt_human(active * getpagesize(), 1024);
+ }
+#endif
diff --git a/slstatuso/components/ram.o b/slstatuso/components/ram.o
new file mode 100644
index 0000000..362bc0d
--- /dev/null
+++ b/slstatuso/components/ram.o
Binary files differ
diff --git a/slstatuso/components/run_command.c b/slstatuso/components/run_command.c
new file mode 100755
index 0000000..93bf6da
--- /dev/null
+++ b/slstatuso/components/run_command.c
@@ -0,0 +1,31 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+run_command(const char *cmd)
+{
+ char *p;
+ FILE *fp;
+
+ if (!(fp = popen(cmd, "r"))) {
+ warn("popen '%s':", cmd);
+ return NULL;
+ }
+
+ p = fgets(buf, sizeof(buf) - 1, fp);
+ if (pclose(fp) < 0) {
+ warn("pclose '%s':", cmd);
+ return NULL;
+ }
+ if (!p)
+ return NULL;
+
+ if ((p = strrchr(buf, '\n')))
+ p[0] = '\0';
+
+ return buf[0] ? buf : NULL;
+}
diff --git a/slstatuso/components/run_command.o b/slstatuso/components/run_command.o
new file mode 100644
index 0000000..44ff9e5
--- /dev/null
+++ b/slstatuso/components/run_command.o
Binary files differ
diff --git a/slstatuso/components/swap.c b/slstatuso/components/swap.c
new file mode 100755
index 0000000..f270d93
--- /dev/null
+++ b/slstatuso/components/swap.c
@@ -0,0 +1,274 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(__linux__)
+ static int
+ get_swap_info(long *s_total, long *s_free, long *s_cached)
+ {
+ FILE *fp;
+ struct {
+ const char *name;
+ const size_t len;
+ long *var;
+ } ent[] = {
+ { "SwapTotal", sizeof("SwapTotal") - 1, s_total },
+ { "SwapFree", sizeof("SwapFree") - 1, s_free },
+ { "SwapCached", sizeof("SwapCached") - 1, s_cached },
+ };
+ size_t line_len = 0, i, left;
+ char *line = NULL;
+
+ /* get number of fields we want to extract */
+ for (i = 0, left = 0; i < LEN(ent); i++)
+ if (ent[i].var)
+ left++;
+
+ if (!(fp = fopen("/proc/meminfo", "r"))) {
+ warn("fopen '/proc/meminfo':");
+ return 1;
+ }
+
+ /* read file line by line and extract field information */
+ while (left > 0 && getline(&line, &line_len, fp) >= 0) {
+ for (i = 0; i < LEN(ent); i++) {
+ if (ent[i].var &&
+ !strncmp(line, ent[i].name, ent[i].len)) {
+ sscanf(line + ent[i].len + 1,
+ "%ld kB\n", ent[i].var);
+ left--;
+ break;
+ }
+ }
+ }
+ free(line);
+ if (ferror(fp)) {
+ warn("getline '/proc/meminfo':");
+ return 1;
+ }
+
+ fclose(fp);
+ return 0;
+ }
+
+ const char *
+ swap_free(const char *unused)
+ {
+ long free;
+
+ if (get_swap_info(NULL, &free, NULL))
+ return NULL;
+
+ return fmt_human(free * 1024, 1024);
+ }
+
+ const char *
+ swap_perc(const char *unused)
+ {
+ long total, free, cached;
+
+ if (get_swap_info(&total, &free, &cached) || total == 0)
+ return NULL;
+
+ return bprintf("%d", 100 * (total - free - cached) / total);
+ }
+
+ const char *
+ swap_total(const char *unused)
+ {
+ long total;
+
+ if (get_swap_info(&total, NULL, NULL))
+ return NULL;
+
+ return fmt_human(total * 1024, 1024);
+ }
+
+ const char *
+ swap_used(const char *unused)
+ {
+ long total, free, cached;
+
+ if (get_swap_info(&total, &free, &cached))
+ return NULL;
+
+ return fmt_human((total - free - cached) * 1024, 1024);
+ }
+#elif defined(__OpenBSD__)
+ #include <stdlib.h>
+ #include <sys/swap.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+
+ static int
+ getstats(int *total, int *used)
+ {
+ struct swapent *sep, *fsep;
+ int rnswap, nswap, i;
+
+ if ((nswap = swapctl(SWAP_NSWAP, 0, 0)) < 1) {
+ warn("swaptctl 'SWAP_NSWAP':");
+ return 1;
+ }
+ if (!(fsep = sep = calloc(nswap, sizeof(*sep)))) {
+ warn("calloc 'nswap':");
+ return 1;
+ }
+ if ((rnswap = swapctl(SWAP_STATS, (void *)sep, nswap)) < 0) {
+ warn("swapctl 'SWAP_STATA':");
+ return 1;
+ }
+ if (nswap != rnswap) {
+ warn("getstats: SWAP_STATS != SWAP_NSWAP");
+ return 1;
+ }
+
+ *total = 0;
+ *used = 0;
+
+ for (i = 0; i < rnswap; i++) {
+ *total += sep->se_nblks >> 1;
+ *used += sep->se_inuse >> 1;
+ }
+
+ free(fsep);
+
+ return 0;
+ }
+
+ const char *
+ swap_free(const char *unused)
+ {
+ int total, used;
+
+ if (getstats(&total, &used))
+ return NULL;
+
+ return fmt_human((total - used) * 1024, 1024);
+ }
+
+ const char *
+ swap_perc(const char *unused)
+ {
+ int total, used;
+
+ if (getstats(&total, &used))
+ return NULL;
+
+ if (total == 0)
+ return NULL;
+
+ return bprintf("%d", 100 * used / total);
+ }
+
+ const char *
+ swap_total(const char *unused)
+ {
+ int total, used;
+
+ if (getstats(&total, &used))
+ return NULL;
+
+ return fmt_human(total * 1024, 1024);
+ }
+
+ const char *
+ swap_used(const char *unused)
+ {
+ int total, used;
+
+ if (getstats(&total, &used))
+ return NULL;
+
+ return fmt_human(used * 1024, 1024);
+ }
+#elif defined(__FreeBSD__)
+ #include <fcntl.h>
+ #include <kvm.h>
+ #include <stdlib.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+
+ static int getswapinfo(struct kvm_swap *swap_info, size_t size)
+ {
+ kvm_t *kd;
+
+ kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, NULL);
+ if (kd == NULL) {
+ warn("kvm_openfiles '/dev/null':");
+ return 0;
+ }
+
+ if (kvm_getswapinfo(kd, swap_info, size, 0 /* Unused flags */) < 0) {
+ warn("kvm_getswapinfo:");
+ kvm_close(kd);
+ return 0;
+ }
+
+ kvm_close(kd);
+ return 1;
+ }
+
+ const char *
+ swap_free(const char *unused)
+ {
+ struct kvm_swap swap_info[1];
+ long used, total;
+
+ if (!getswapinfo(swap_info, 1))
+ return NULL;
+
+ total = swap_info[0].ksw_total;
+ used = swap_info[0].ksw_used;
+
+ return fmt_human((total - used) * getpagesize(), 1024);
+ }
+
+ const char *
+ swap_perc(const char *unused)
+ {
+ struct kvm_swap swap_info[1];
+ long used, total;
+
+ if (!getswapinfo(swap_info, 1))
+ return NULL;
+
+ total = swap_info[0].ksw_total;
+ used = swap_info[0].ksw_used;
+
+ return bprintf("%d", used * 100 / total);
+ }
+
+ const char *
+ swap_total(const char *unused)
+ {
+ struct kvm_swap swap_info[1];
+ long total;
+
+ if (!getswapinfo(swap_info, 1))
+ return NULL;
+
+ total = swap_info[0].ksw_total;
+
+ return fmt_human(total * getpagesize(), 1024);
+ }
+
+ const char *
+ swap_used(const char *unused)
+ {
+ struct kvm_swap swap_info[1];
+ long used;
+
+ if (!getswapinfo(swap_info, 1))
+ return NULL;
+
+ used = swap_info[0].ksw_used;
+
+ return fmt_human(used * getpagesize(), 1024);
+ }
+#endif
diff --git a/slstatuso/components/swap.o b/slstatuso/components/swap.o
new file mode 100644
index 0000000..4cc30f3
--- /dev/null
+++ b/slstatuso/components/swap.o
Binary files differ
diff --git a/slstatuso/components/temperature.c b/slstatuso/components/temperature.c
new file mode 100755
index 0000000..7cf1394
--- /dev/null
+++ b/slstatuso/components/temperature.c
@@ -0,0 +1,73 @@
+/* See LICENSE file for copyright and license details. */
+#include <stddef.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+
+#if defined(__linux__)
+ #include <stdint.h>
+
+ const char *
+ temp(const char *file)
+ {
+ uintmax_t temp;
+
+ if (pscanf(file, "%ju", &temp) != 1)
+ return NULL;
+
+ return bprintf("%ju", temp / 1000);
+ }
+#elif defined(__OpenBSD__)
+ #include <stdio.h>
+ #include <sys/time.h> /* before <sys/sensors.h> for struct timeval */
+ #include <sys/sensors.h>
+ #include <sys/sysctl.h>
+
+ const char *
+ temp(const char *unused)
+ {
+ int mib[5];
+ size_t size;
+ struct sensor temp;
+
+ mib[0] = CTL_HW;
+ mib[1] = HW_SENSORS;
+ mib[2] = 0; /* cpu0 */
+ mib[3] = SENSOR_TEMP;
+ mib[4] = 0; /* temp0 */
+
+ size = sizeof(temp);
+
+ if (sysctl(mib, 5, &temp, &size, NULL, 0) < 0) {
+ warn("sysctl 'SENSOR_TEMP':");
+ return NULL;
+ }
+
+ /* kelvin to celsius */
+ return bprintf("%d", (int)((float)(temp.value-273150000) / 1E6));
+ }
+#elif defined(__FreeBSD__)
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <sys/sysctl.h>
+
+ #define ACPI_TEMP "hw.acpi.thermal.%s.temperature"
+
+ const char *
+ temp(const char *zone)
+ {
+ char buf[256];
+ int temp;
+ size_t len;
+
+ len = sizeof(temp);
+ snprintf(buf, sizeof(buf), ACPI_TEMP, zone);
+ if (sysctlbyname(buf, &temp, &len, NULL, 0) < 0
+ || !len)
+ return NULL;
+
+ /* kelvin to decimal celcius */
+ return bprintf("%d.%d", (temp - 2731) / 10, abs((temp - 2731) % 10));
+ }
+#endif
diff --git a/slstatuso/components/temperature.o b/slstatuso/components/temperature.o
new file mode 100644
index 0000000..4be7822
--- /dev/null
+++ b/slstatuso/components/temperature.o
Binary files differ
diff --git a/slstatuso/components/uptime.c b/slstatuso/components/uptime.c
new file mode 100755
index 0000000..d97e5e8
--- /dev/null
+++ b/slstatuso/components/uptime.c
@@ -0,0 +1,34 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(CLOCK_BOOTTIME)
+ #define UPTIME_FLAG CLOCK_BOOTTIME
+#elif defined(CLOCK_UPTIME)
+ #define UPTIME_FLAG CLOCK_UPTIME
+#else
+ #define UPTIME_FLAG CLOCK_MONOTONIC
+#endif
+
+const char *
+uptime(const char *unused)
+{
+ char warn_buf[256];
+ uintmax_t h, m;
+ struct timespec uptime;
+
+ if (clock_gettime(UPTIME_FLAG, &uptime) < 0) {
+ snprintf(warn_buf, 256, "clock_gettime %d", UPTIME_FLAG);
+ warn(warn_buf);
+ return NULL;
+ }
+
+ h = uptime.tv_sec / 3600;
+ m = uptime.tv_sec % 3600 / 60;
+
+ return bprintf("%juh %jum", h, m);
+}
diff --git a/slstatuso/components/uptime.o b/slstatuso/components/uptime.o
new file mode 100644
index 0000000..dc45ddc
--- /dev/null
+++ b/slstatuso/components/uptime.o
Binary files differ
diff --git a/slstatuso/components/user.c b/slstatuso/components/user.c
new file mode 100755
index 0000000..3517495
--- /dev/null
+++ b/slstatuso/components/user.c
@@ -0,0 +1,33 @@
+/* See LICENSE file for copyright and license details. */
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+gid(const char *unused)
+{
+ return bprintf("%d", getgid());
+}
+
+const char *
+username(const char *unused)
+{
+ struct passwd *pw;
+
+ if (!(pw = getpwuid(geteuid()))) {
+ warn("getpwuid '%d':", geteuid());
+ return NULL;
+ }
+
+ return bprintf("%s", pw->pw_name);
+}
+
+const char *
+uid(const char *unused)
+{
+ return bprintf("%d", geteuid());
+}
diff --git a/slstatuso/components/user.o b/slstatuso/components/user.o
new file mode 100644
index 0000000..777126d
--- /dev/null
+++ b/slstatuso/components/user.o
Binary files differ
diff --git a/slstatuso/components/volume.c b/slstatuso/components/volume.c
new file mode 100755
index 0000000..6cec556
--- /dev/null
+++ b/slstatuso/components/volume.c
@@ -0,0 +1,219 @@
+/* See LICENSE file for copyright and license details. */
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(__OpenBSD__) | defined(__FreeBSD__)
+ #include <poll.h>
+ #include <sndio.h>
+ #include <stdlib.h>
+ #include <sys/queue.h>
+
+ struct control {
+ LIST_ENTRY(control) next;
+ unsigned int addr;
+ #define CTRL_NONE 0
+ #define CTRL_LEVEL 1
+ #define CTRL_MUTE 2
+ unsigned int type;
+ unsigned int maxval;
+ unsigned int val;
+ };
+
+ static LIST_HEAD(, control) controls = LIST_HEAD_INITIALIZER(controls);
+ static struct pollfd *pfds;
+ static struct sioctl_hdl *hdl;
+ static int initialized;
+
+ /*
+ * Call-back to obtain the description of all audio controls.
+ */
+ static void
+ ondesc(void *unused, struct sioctl_desc *desc, int val)
+ {
+ struct control *c, *ctmp;
+ unsigned int type = CTRL_NONE;
+
+ if (desc == NULL)
+ return;
+
+ /* Delete existing audio control with the same address. */
+ LIST_FOREACH_SAFE(c, &controls, next, ctmp) {
+ if (desc->addr == c->addr) {
+ LIST_REMOVE(c, next);
+ free(c);
+ break;
+ }
+ }
+
+ /* Only match output.level and output.mute audio controls. */
+ if (desc->group[0] != 0 ||
+ strcmp(desc->node0.name, "output") != 0)
+ return;
+ if (desc->type == SIOCTL_NUM &&
+ strcmp(desc->func, "level") == 0)
+ type = CTRL_LEVEL;
+ else if (desc->type == SIOCTL_SW &&
+ strcmp(desc->func, "mute") == 0)
+ type = CTRL_MUTE;
+ else
+ return;
+
+ c = malloc(sizeof(struct control));
+ if (c == NULL) {
+ warn("sndio: failed to allocate audio control\n");
+ return;
+ }
+
+ c->addr = desc->addr;
+ c->type = type;
+ c->maxval = desc->maxval;
+ c->val = val;
+ LIST_INSERT_HEAD(&controls, c, next);
+ }
+
+ /*
+ * Call-back invoked whenever an audio control changes.
+ */
+ static void
+ onval(void *unused, unsigned int addr, unsigned int val)
+ {
+ struct control *c;
+
+ LIST_FOREACH(c, &controls, next) {
+ if (c->addr == addr)
+ break;
+ }
+ c->val = val;
+ }
+
+ static void
+ cleanup(void)
+ {
+ struct control *c;
+
+ if (hdl) {
+ sioctl_close(hdl);
+ hdl = NULL;
+ }
+
+ free(pfds);
+ pfds = NULL;
+
+ while (!LIST_EMPTY(&controls)) {
+ c = LIST_FIRST(&controls);
+ LIST_REMOVE(c, next);
+ free(c);
+ }
+ }
+
+ static int
+ init(void)
+ {
+ hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0);
+ if (hdl == NULL) {
+ warn("sndio: cannot open device");
+ goto failed;
+ }
+
+ if (!sioctl_ondesc(hdl, ondesc, NULL)) {
+ warn("sndio: cannot set control description call-back");
+ goto failed;
+ }
+
+ if (!sioctl_onval(hdl, onval, NULL)) {
+ warn("sndio: cannot set control values call-back");
+ goto failed;
+ }
+
+ pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
+ if (pfds == NULL) {
+ warn("sndio: cannot allocate pollfd structures");
+ goto failed;
+ }
+
+ return 1;
+ failed:
+ cleanup();
+ return 0;
+ }
+
+ const char *
+ vol_perc(const char *unused)
+ {
+ struct control *c;
+ int n, v, value;
+
+ if (!initialized)
+ initialized = init();
+
+ if (hdl == NULL)
+ return NULL;
+
+ n = sioctl_pollfd(hdl, pfds, POLLIN);
+ if (n > 0) {
+ n = poll(pfds, n, 0);
+ if (n > 0) {
+ if (sioctl_revents(hdl, pfds) & POLLHUP) {
+ warn("sndio: disconnected");
+ cleanup();
+ initialized = 0;
+ return NULL;
+ }
+ }
+ }
+
+ value = 100;
+ LIST_FOREACH(c, &controls, next) {
+ if (c->type == CTRL_MUTE && c->val == 1)
+ value = 0;
+ else if (c->type == CTRL_LEVEL) {
+ v = (c->val * 100 + c->maxval / 2) / c->maxval;
+ /* For multiple channels return the minimum. */
+ if (v < value)
+ value = v;
+ }
+ }
+
+ return bprintf("%d", value);
+ }
+#else
+ #include <sys/soundcard.h>
+
+ const char *
+ vol_perc(const char *card)
+ {
+ size_t i;
+ int v, afd, devmask;
+ char *vnames[] = SOUND_DEVICE_NAMES;
+
+ if ((afd = open(card, O_RDONLY | O_NONBLOCK)) < 0) {
+ warn("open '%s':", card);
+ return NULL;
+ }
+
+ if (ioctl(afd, (int)SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
+ warn("ioctl 'SOUND_MIXER_READ_DEVMASK':");
+ close(afd);
+ return NULL;
+ }
+ for (i = 0; i < LEN(vnames); i++) {
+ if (devmask & (1 << i) && !strcmp("vol", vnames[i])) {
+ if (ioctl(afd, MIXER_READ(i), &v) < 0) {
+ warn("ioctl 'MIXER_READ(%ld)':", i);
+ close(afd);
+ return NULL;
+ }
+ }
+ }
+
+ close(afd);
+
+ return bprintf("%d", v & 0xff);
+ }
+#endif
diff --git a/slstatuso/components/volume.o b/slstatuso/components/volume.o
new file mode 100644
index 0000000..d409e19
--- /dev/null
+++ b/slstatuso/components/volume.o
Binary files differ
diff --git a/slstatuso/components/wifi.c b/slstatuso/components/wifi.c
new file mode 100755
index 0000000..4543d32
--- /dev/null
+++ b/slstatuso/components/wifi.c
@@ -0,0 +1,267 @@
+/* See LICENSE file for copyright and license details. */
+#include <ifaddrs.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#define RSSI_TO_PERC(rssi) \
+ rssi >= -50 ? 100 : \
+ (rssi <= -100 ? 0 : \
+ (2 * (rssi + 100)))
+
+#if defined(__linux__)
+ #include <limits.h>
+ #include <linux/wireless.h>
+
+ #define NET_OPERSTATE "/sys/class/net/%s/operstate"
+
+ const char *
+ wifi_perc(const char *interface)
+ {
+ int cur;
+ size_t i;
+ char *p, *datastart;
+ char path[PATH_MAX];
+ char status[5];
+ FILE *fp;
+
+ if (esnprintf(path, sizeof(path), NET_OPERSTATE, interface) < 0)
+ return NULL;
+ if (!(fp = fopen(path, "r"))) {
+ warn("fopen '%s':", path);
+ return NULL;
+ }
+ p = fgets(status, 5, fp);
+ fclose(fp);
+ if (!p || strcmp(status, "up\n") != 0)
+ return NULL;
+
+ if (!(fp = fopen("/proc/net/wireless", "r"))) {
+ warn("fopen '/proc/net/wireless':");
+ return NULL;
+ }
+
+ for (i = 0; i < 3; i++)
+ if (!(p = fgets(buf, sizeof(buf) - 1, fp)))
+ break;
+
+ fclose(fp);
+ if (i < 2 || !p)
+ return NULL;
+
+ if (!(datastart = strstr(buf, interface)))
+ return NULL;
+
+ datastart = (datastart+(strlen(interface)+1));
+ sscanf(datastart + 1, " %*d %d %*d %*d\t\t %*d\t "
+ "%*d\t\t%*d\t\t %*d\t %*d\t\t %*d", &cur);
+
+ /* 70 is the max of /proc/net/wireless */
+ return bprintf("%d", (int)((float)cur / 70 * 100));
+ }
+
+ const char *
+ wifi_essid(const char *interface)
+ {
+ static char id[IW_ESSID_MAX_SIZE+1];
+ int sockfd;
+ struct iwreq wreq;
+
+ memset(&wreq, 0, sizeof(struct iwreq));
+ wreq.u.essid.length = IW_ESSID_MAX_SIZE+1;
+ if (esnprintf(wreq.ifr_name, sizeof(wreq.ifr_name), "%s",
+ interface) < 0)
+ return NULL;
+
+ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ warn("socket 'AF_INET':");
+ return NULL;
+ }
+ wreq.u.essid.pointer = id;
+ if (ioctl(sockfd,SIOCGIWESSID, &wreq) < 0) {
+ warn("ioctl 'SIOCGIWESSID':");
+ close(sockfd);
+ return NULL;
+ }
+
+ close(sockfd);
+
+ if (!strcmp(id, ""))
+ return NULL;
+
+ return id;
+ }
+#elif defined(__OpenBSD__)
+ #include <net/if.h>
+ #include <net/if_media.h>
+ #include <net80211/ieee80211.h>
+ #include <sys/select.h> /* before <sys/ieee80211_ioctl.h> for NBBY */
+ #include <net80211/ieee80211_ioctl.h>
+ #include <stdlib.h>
+ #include <sys/types.h>
+
+ static int
+ load_ieee80211_nodereq(const char *interface, struct ieee80211_nodereq *nr)
+ {
+ struct ieee80211_bssid bssid;
+ int sockfd;
+ uint8_t zero_bssid[IEEE80211_ADDR_LEN];
+
+ memset(&bssid, 0, sizeof(bssid));
+ memset(nr, 0, sizeof(struct ieee80211_nodereq));
+ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ warn("socket 'AF_INET':");
+ return 0;
+ }
+ strlcpy(bssid.i_name, interface, sizeof(bssid.i_name));
+ if ((ioctl(sockfd, SIOCG80211BSSID, &bssid)) < 0) {
+ warn("ioctl 'SIOCG80211BSSID':");
+ close(sockfd);
+ return 0;
+ }
+ memset(&zero_bssid, 0, sizeof(zero_bssid));
+ if (memcmp(bssid.i_bssid, zero_bssid,
+ IEEE80211_ADDR_LEN) == 0) {
+ close(sockfd);
+ return 0;
+ }
+ strlcpy(nr->nr_ifname, interface, sizeof(nr->nr_ifname));
+ memcpy(&nr->nr_macaddr, bssid.i_bssid, sizeof(nr->nr_macaddr));
+ if ((ioctl(sockfd, SIOCG80211NODE, nr)) < 0 && nr->nr_rssi) {
+ warn("ioctl 'SIOCG80211NODE':");
+ close(sockfd);
+ return 0;
+ }
+
+ return close(sockfd), 1;
+ }
+
+ const char *
+ wifi_perc(const char *interface)
+ {
+ struct ieee80211_nodereq nr;
+ int q;
+
+ if (load_ieee80211_nodereq(interface, &nr)) {
+ if (nr.nr_max_rssi)
+ q = IEEE80211_NODEREQ_RSSI(&nr);
+ else
+ q = RSSI_TO_PERC(nr.nr_rssi);
+
+ return bprintf("%d", q);
+ }
+
+ return NULL;
+ }
+
+ const char *
+ wifi_essid(const char *interface)
+ {
+ struct ieee80211_nodereq nr;
+
+ if (load_ieee80211_nodereq(interface, &nr))
+ return bprintf("%s", nr.nr_nwid);
+
+ return NULL;
+ }
+#elif defined(__FreeBSD__)
+ #include <net/if.h>
+ #include <net80211/ieee80211_ioctl.h>
+
+ int
+ load_ieee80211req(int sock, const char *interface, void *data, int type, size_t *len)
+ {
+ char warn_buf[256];
+ struct ieee80211req ireq;
+ memset(&ireq, 0, sizeof(ireq));
+ ireq.i_type = type;
+ ireq.i_data = (caddr_t) data;
+ ireq.i_len = *len;
+
+ strlcpy(ireq.i_name, interface, sizeof(ireq.i_name));
+ if (ioctl(sock, SIOCG80211, &ireq) < 0) {
+ snprintf(warn_buf, sizeof(warn_buf),
+ "ioctl: 'SIOCG80211': %d", type);
+ warn(warn_buf);
+ return 0;
+ }
+
+ *len = ireq.i_len;
+ return 1;
+ }
+
+ const char *
+ wifi_perc(const char *interface)
+ {
+ union {
+ struct ieee80211req_sta_req sta;
+ uint8_t buf[24 * 1024];
+ } info;
+ uint8_t bssid[IEEE80211_ADDR_LEN];
+ int rssi_dbm;
+ int sockfd;
+ size_t len;
+ const char *fmt;
+
+ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ warn("socket 'AF_INET':");
+ return NULL;
+ }
+
+ /* Retreive MAC address of interface */
+ len = IEEE80211_ADDR_LEN;
+ fmt = NULL;
+ if (load_ieee80211req(sockfd, interface, &bssid, IEEE80211_IOC_BSSID, &len))
+ {
+ /* Retrieve info on station with above BSSID */
+ memset(&info, 0, sizeof(info));
+ memcpy(info.sta.is_u.macaddr, bssid, sizeof(bssid));
+
+ len = sizeof(info);
+ if (load_ieee80211req(sockfd, interface, &info, IEEE80211_IOC_STA_INFO, &len)) {
+ rssi_dbm = info.sta.info[0].isi_noise +
+ info.sta.info[0].isi_rssi / 2;
+
+ fmt = bprintf("%d", RSSI_TO_PERC(rssi_dbm));
+ }
+ }
+
+ close(sockfd);
+ return fmt;
+ }
+
+ const char *
+ wifi_essid(const char *interface)
+ {
+ char ssid[IEEE80211_NWID_LEN + 1];
+ size_t len;
+ int sockfd;
+ const char *fmt;
+
+ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ warn("socket 'AF_INET':");
+ return NULL;
+ }
+
+ fmt = NULL;
+ len = sizeof(ssid);
+ memset(&ssid, 0, len);
+ if (load_ieee80211req(sockfd, interface, &ssid, IEEE80211_IOC_SSID, &len)) {
+ if (len < sizeof(ssid))
+ len += 1;
+ else
+ len = sizeof(ssid);
+
+ ssid[len - 1] = '\0';
+ fmt = bprintf("%s", ssid);
+ }
+
+ close(sockfd);
+ return fmt;
+ }
+#endif
diff --git a/slstatuso/components/wifi.o b/slstatuso/components/wifi.o
new file mode 100644
index 0000000..9fec196
--- /dev/null
+++ b/slstatuso/components/wifi.o
Binary files differ
diff --git a/slstatuso/config.def.h b/slstatuso/config.def.h
new file mode 100755
index 0000000..671994d
--- /dev/null
+++ b/slstatuso/config.def.h
@@ -0,0 +1,77 @@
+/* See LICENSE file for copyright and license details. */
+
+/* interval between updates (in ms) */
+const unsigned int interval = 1000;
+
+/* text to show if no value can be retrieved */
+static const char unknown_str[] = "n/a";
+
+/* maximum output string length */
+#define MAXLEN 2048
+
+/*
+ * function description argument (example)
+ *
+ * battery_perc battery percentage battery name (BAT0)
+ * NULL on OpenBSD/FreeBSD
+ * battery_state battery charging state battery name (BAT0)
+ * NULL on OpenBSD/FreeBSD
+ * battery_remaining battery remaining HH:MM battery name (BAT0)
+ * NULL on OpenBSD/FreeBSD
+ * cat read arbitrary file path
+ * cpu_perc cpu usage in percent NULL
+ * cpu_freq cpu frequency in MHz NULL
+ * datetime date and time format string (%F %T)
+ * disk_free free disk space in GB mountpoint path (/)
+ * disk_perc disk usage in percent mountpoint path (/)
+ * disk_total total disk space in GB mountpoint path (/")
+ * disk_used used disk space in GB mountpoint path (/)
+ * entropy available entropy NULL
+ * gid GID of current user NULL
+ * hostname hostname NULL
+ * ipv4 IPv4 address interface name (eth0)
+ * ipv6 IPv6 address interface name (eth0)
+ * kernel_release `uname -r` NULL
+ * keyboard_indicators caps/num lock indicators format string (c?n?)
+ * see keyboard_indicators.c
+ * keymap layout (variant) of current NULL
+ * keymap
+ * load_avg load average NULL
+ * netspeed_rx receive network speed interface name (wlan0)
+ * netspeed_tx transfer network speed interface name (wlan0)
+ * num_files number of files in a directory path
+ * (/home/foo/Inbox/cur)
+ * ram_free free memory in GB NULL
+ * ram_perc memory usage in percent NULL
+ * ram_total total memory size in GB NULL
+ * ram_used used memory in GB NULL
+ * run_command custom shell command command (echo foo)
+ * swap_free free swap in GB NULL
+ * swap_perc swap usage in percent NULL
+ * swap_total total swap size in GB NULL
+ * swap_used used swap in GB NULL
+ * temp temperature in degree celsius sensor file
+ * (/sys/class/thermal/...)
+ * NULL on OpenBSD
+ * thermal zone on FreeBSD
+ * (tz0, tz1, etc.)
+ * uid UID of current user NULL
+ * uptime system uptime NULL
+ * username username of current user NULL
+ * vol_perc OSS/ALSA volume in percent mixer file (/dev/mixer)
+ * NULL on OpenBSD/FreeBSD
+ * wifi_perc WiFi signal in percent interface name (wlan0)
+ * wifi_essid WiFi ESSID interface name (wlan0)
+ */
+static const struct arg args[] = {
+ /* function argument */
+ { cpu_perc, " CPU %3s%% | ", NULL },
+ { ram_perc, "RAM %2s%% | ", NULL },
+ { temp, "%s° C | ", "/sys/class/thermal/thermal_zone0/temp" },
+ { vol_perc, "VOL %2s%% | ", NULL },
+ { wifi_essid, "%s ", "iwn0" },
+ { wifi_perc, "(%2s%%) | ", "iwn0" },
+ { battery_perc, "BAT %2s%% ", "BAT0" },
+ { battery_state, "(%s) | ", "BAT0" },
+ { datetime, "%s ", "%F %T" },
+};
diff --git a/slstatuso/config.h b/slstatuso/config.h
new file mode 100755
index 0000000..671994d
--- /dev/null
+++ b/slstatuso/config.h
@@ -0,0 +1,77 @@
+/* See LICENSE file for copyright and license details. */
+
+/* interval between updates (in ms) */
+const unsigned int interval = 1000;
+
+/* text to show if no value can be retrieved */
+static const char unknown_str[] = "n/a";
+
+/* maximum output string length */
+#define MAXLEN 2048
+
+/*
+ * function description argument (example)
+ *
+ * battery_perc battery percentage battery name (BAT0)
+ * NULL on OpenBSD/FreeBSD
+ * battery_state battery charging state battery name (BAT0)
+ * NULL on OpenBSD/FreeBSD
+ * battery_remaining battery remaining HH:MM battery name (BAT0)
+ * NULL on OpenBSD/FreeBSD
+ * cat read arbitrary file path
+ * cpu_perc cpu usage in percent NULL
+ * cpu_freq cpu frequency in MHz NULL
+ * datetime date and time format string (%F %T)
+ * disk_free free disk space in GB mountpoint path (/)
+ * disk_perc disk usage in percent mountpoint path (/)
+ * disk_total total disk space in GB mountpoint path (/")
+ * disk_used used disk space in GB mountpoint path (/)
+ * entropy available entropy NULL
+ * gid GID of current user NULL
+ * hostname hostname NULL
+ * ipv4 IPv4 address interface name (eth0)
+ * ipv6 IPv6 address interface name (eth0)
+ * kernel_release `uname -r` NULL
+ * keyboard_indicators caps/num lock indicators format string (c?n?)
+ * see keyboard_indicators.c
+ * keymap layout (variant) of current NULL
+ * keymap
+ * load_avg load average NULL
+ * netspeed_rx receive network speed interface name (wlan0)
+ * netspeed_tx transfer network speed interface name (wlan0)
+ * num_files number of files in a directory path
+ * (/home/foo/Inbox/cur)
+ * ram_free free memory in GB NULL
+ * ram_perc memory usage in percent NULL
+ * ram_total total memory size in GB NULL
+ * ram_used used memory in GB NULL
+ * run_command custom shell command command (echo foo)
+ * swap_free free swap in GB NULL
+ * swap_perc swap usage in percent NULL
+ * swap_total total swap size in GB NULL
+ * swap_used used swap in GB NULL
+ * temp temperature in degree celsius sensor file
+ * (/sys/class/thermal/...)
+ * NULL on OpenBSD
+ * thermal zone on FreeBSD
+ * (tz0, tz1, etc.)
+ * uid UID of current user NULL
+ * uptime system uptime NULL
+ * username username of current user NULL
+ * vol_perc OSS/ALSA volume in percent mixer file (/dev/mixer)
+ * NULL on OpenBSD/FreeBSD
+ * wifi_perc WiFi signal in percent interface name (wlan0)
+ * wifi_essid WiFi ESSID interface name (wlan0)
+ */
+static const struct arg args[] = {
+ /* function argument */
+ { cpu_perc, " CPU %3s%% | ", NULL },
+ { ram_perc, "RAM %2s%% | ", NULL },
+ { temp, "%s° C | ", "/sys/class/thermal/thermal_zone0/temp" },
+ { vol_perc, "VOL %2s%% | ", NULL },
+ { wifi_essid, "%s ", "iwn0" },
+ { wifi_perc, "(%2s%%) | ", "iwn0" },
+ { battery_perc, "BAT %2s%% ", "BAT0" },
+ { battery_state, "(%s) | ", "BAT0" },
+ { datetime, "%s ", "%F %T" },
+};
diff --git a/slstatuso/config.mk b/slstatuso/config.mk
new file mode 100755
index 0000000..dd6fcba
--- /dev/null
+++ b/slstatuso/config.mk
@@ -0,0 +1,22 @@
+# slstatus version
+VERSION = 0
+
+# customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+
+# flags
+CPPFLAGS = -I$(X11INC) -D_DEFAULT_SOURCE
+CFLAGS = -std=c99 -pedantic -Wall -Wextra -Wno-unused-parameter -Os
+LDFLAGS = -L$(X11LIB) -s
+# OpenBSD: add -lsndio
+# FreeBSD: add -lkvm -lsndio
+LDLIBS = -lX11 -lsndio
+
+# compiler and linker
+CC = cc
diff --git a/slstatuso/slstatus b/slstatuso/slstatus
new file mode 100755
index 0000000..1a91f11
--- /dev/null
+++ b/slstatuso/slstatus
Binary files differ
diff --git a/slstatuso/slstatus.1 b/slstatuso/slstatus.1
new file mode 100755
index 0000000..d802037
--- /dev/null
+++ b/slstatuso/slstatus.1
@@ -0,0 +1,28 @@
+.Dd 2020-06-23
+.Dt SLSTATUS 1
+.Os
+.Sh NAME
+.Nm slstatus
+.Nd suckless status monitor
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Op Fl 1
+.Sh DESCRIPTION
+.Nm
+is a suckless status monitor for window managers that use WM_NAME (e.g. dwm) or
+stdin to fill the status bar.
+By default,
+.Nm
+outputs to WM_NAME.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl s
+Write to stdout instead of WM_NAME.
+.It Fl 1
+Write once to stdout and quit.
+.El
+.Sh CUSTOMIZATION
+.Nm
+can be customized by creating a custom config.h and (re)compiling the source
+code. This keeps it fast, secure and simple.
diff --git a/slstatuso/slstatus.c b/slstatuso/slstatus.c
new file mode 100755
index 0000000..cb54f29
--- /dev/null
+++ b/slstatuso/slstatus.c
@@ -0,0 +1,132 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <X11/Xlib.h>
+
+#include "arg.h"
+#include "slstatus.h"
+#include "util.h"
+
+struct arg {
+ const char *(*func)(const char *);
+ const char *fmt;
+ const char *args;
+};
+
+char buf[1024];
+static volatile sig_atomic_t done;
+static Display *dpy;
+
+#include "config.h"
+
+static void
+terminate(const int signo)
+{
+ if (signo != SIGUSR1)
+ done = 1;
+}
+
+static void
+difftimespec(struct timespec *res, struct timespec *a, struct timespec *b)
+{
+ res->tv_sec = a->tv_sec - b->tv_sec - (a->tv_nsec < b->tv_nsec);
+ res->tv_nsec = a->tv_nsec - b->tv_nsec +
+ (a->tv_nsec < b->tv_nsec) * 1E9;
+}
+
+static void
+usage(void)
+{
+ die("usage: %s [-s] [-1]", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction act;
+ struct timespec start, current, diff, intspec, wait;
+ size_t i, len;
+ int sflag, ret;
+ char status[MAXLEN];
+ const char *res;
+
+ sflag = 0;
+ ARGBEGIN {
+ case '1':
+ done = 1;
+ /* FALLTHROUGH */
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ if (argc)
+ usage();
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = terminate;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ act.sa_flags |= SA_RESTART;
+ sigaction(SIGUSR1, &act, NULL);
+
+ if (!sflag && !(dpy = XOpenDisplay(NULL)))
+ die("XOpenDisplay: Failed to open display");
+
+ do {
+ if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
+ die("clock_gettime:");
+
+ status[0] = '\0';
+ for (i = len = 0; i < LEN(args); i++) {
+ if (!(res = args[i].func(args[i].args)))
+ res = unknown_str;
+
+ if ((ret = esnprintf(status + len, sizeof(status) - len,
+ args[i].fmt, res)) < 0)
+ break;
+
+ len += ret;
+ }
+
+ if (sflag) {
+ puts(status);
+ fflush(stdout);
+ if (ferror(stdout))
+ die("puts:");
+ } else {
+ if (XStoreName(dpy, DefaultRootWindow(dpy), status) < 0)
+ die("XStoreName: Allocation failed");
+ XFlush(dpy);
+ }
+
+ if (!done) {
+ if (clock_gettime(CLOCK_MONOTONIC, &current) < 0)
+ die("clock_gettime:");
+ difftimespec(&diff, &current, &start);
+
+ intspec.tv_sec = interval / 1000;
+ intspec.tv_nsec = (interval % 1000) * 1E6;
+ difftimespec(&wait, &intspec, &diff);
+
+ if (wait.tv_sec >= 0 &&
+ nanosleep(&wait, NULL) < 0 &&
+ errno != EINTR)
+ die("nanosleep:");
+ }
+ } while (!done);
+
+ if (!sflag) {
+ XStoreName(dpy, DefaultRootWindow(dpy), NULL);
+ if (XCloseDisplay(dpy) < 0)
+ die("XCloseDisplay: Failed to close display");
+ }
+
+ return 0;
+}
diff --git a/slstatuso/slstatus.h b/slstatuso/slstatus.h
new file mode 100755
index 0000000..f062e73
--- /dev/null
+++ b/slstatuso/slstatus.h
@@ -0,0 +1,84 @@
+/* See LICENSE file for copyright and license details. */
+
+/* battery */
+const char *battery_perc(const char *);
+const char *battery_state(const char *);
+const char *battery_remaining(const char *);
+
+/* cat */
+const char *cat(const char *path);
+
+/* cpu */
+const char *cpu_freq(const char *unused);
+const char *cpu_perc(const char *unused);
+
+/* datetime */
+const char *datetime(const char *fmt);
+
+/* disk */
+const char *disk_free(const char *path);
+const char *disk_perc(const char *path);
+const char *disk_total(const char *path);
+const char *disk_used(const char *path);
+
+/* entropy */
+const char *entropy(const char *unused);
+
+/* hostname */
+const char *hostname(const char *unused);
+
+/* ip */
+const char *ipv4(const char *interface);
+const char *ipv6(const char *interface);
+
+/* kernel_release */
+const char *kernel_release(const char *unused);
+
+/* keyboard_indicators */
+const char *keyboard_indicators(const char *fmt);
+
+/* keymap */
+const char *keymap(const char *unused);
+
+/* load_avg */
+const char *load_avg(const char *unused);
+
+/* netspeeds */
+const char *netspeed_rx(const char *interface);
+const char *netspeed_tx(const char *interface);
+
+/* num_files */
+const char *num_files(const char *path);
+
+/* ram */
+const char *ram_free(const char *unused);
+const char *ram_perc(const char *unused);
+const char *ram_total(const char *unused);
+const char *ram_used(const char *unused);
+
+/* run_command */
+const char *run_command(const char *cmd);
+
+/* swap */
+const char *swap_free(const char *unused);
+const char *swap_perc(const char *unused);
+const char *swap_total(const char *unused);
+const char *swap_used(const char *unused);
+
+/* temperature */
+const char *temp(const char *);
+
+/* uptime */
+const char *uptime(const char *unused);
+
+/* user */
+const char *gid(const char *unused);
+const char *username(const char *unused);
+const char *uid(const char *unused);
+
+/* volume */
+const char *vol_perc(const char *card);
+
+/* wifi */
+const char *wifi_perc(const char *interface);
+const char *wifi_essid(const char *interface);
diff --git a/slstatuso/slstatus.o b/slstatuso/slstatus.o
new file mode 100644
index 0000000..9c337fe
--- /dev/null
+++ b/slstatuso/slstatus.o
Binary files differ
diff --git a/slstatuso/util.c b/slstatuso/util.c
new file mode 100755
index 0000000..d1f6077
--- /dev/null
+++ b/slstatuso/util.c
@@ -0,0 +1,144 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+char *argv0;
+
+static void
+verr(const char *fmt, va_list ap)
+{
+ if (argv0 && strncmp(fmt, "usage", sizeof("usage") - 1))
+ fprintf(stderr, "%s: ", argv0);
+
+ vfprintf(stderr, fmt, ap);
+
+ if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+}
+
+void
+warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ verr(fmt, ap);
+ va_end(ap);
+}
+
+void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ verr(fmt, ap);
+ va_end(ap);
+
+ exit(1);
+}
+
+static int
+evsnprintf(char *str, size_t size, const char *fmt, va_list ap)
+{
+ int ret;
+
+ ret = vsnprintf(str, size, fmt, ap);
+
+ if (ret < 0) {
+ warn("vsnprintf:");
+ return -1;
+ } else if ((size_t)ret >= size) {
+ warn("vsnprintf: Output truncated");
+ return -1;
+ }
+
+ return ret;
+}
+
+int
+esnprintf(char *str, size_t size, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = evsnprintf(str, size, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+const char *
+bprintf(const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = evsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ return (ret < 0) ? NULL : buf;
+}
+
+const char *
+fmt_human(uintmax_t num, int base)
+{
+ double scaled;
+ size_t i, prefixlen;
+ const char **prefix;
+ const char *prefix_1000[] = { "", "k", "M", "G", "T", "P", "E", "Z",
+ "Y" };
+ const char *prefix_1024[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei",
+ "Zi", "Yi" };
+
+ switch (base) {
+ case 1000:
+ prefix = prefix_1000;
+ prefixlen = LEN(prefix_1000);
+ break;
+ case 1024:
+ prefix = prefix_1024;
+ prefixlen = LEN(prefix_1024);
+ break;
+ default:
+ warn("fmt_human: Invalid base");
+ return NULL;
+ }
+
+ scaled = num;
+ for (i = 0; i < prefixlen && scaled >= base; i++)
+ scaled /= base;
+
+ return bprintf("%.1f %s", scaled, prefix[i]);
+}
+
+int
+pscanf(const char *path, const char *fmt, ...)
+{
+ FILE *fp;
+ va_list ap;
+ int n;
+
+ if (!(fp = fopen(path, "r"))) {
+ warn("fopen '%s':", path);
+ return -1;
+ }
+ va_start(ap, fmt);
+ n = vfscanf(fp, fmt, ap);
+ va_end(ap);
+ fclose(fp);
+
+ return (n == EOF) ? -1 : n;
+}
diff --git a/slstatuso/util.h b/slstatuso/util.h
new file mode 100755
index 0000000..7f1f26c
--- /dev/null
+++ b/slstatuso/util.h
@@ -0,0 +1,16 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+
+extern char buf[1024];
+
+#define LEN(x) (sizeof (x) / sizeof *(x))
+
+extern char *argv0;
+
+void warn(const char *, ...);
+void die(const char *, ...);
+
+int esnprintf(char *str, size_t size, const char *fmt, ...);
+const char *bprintf(const char *fmt, ...);
+const char *fmt_human(uintmax_t num, int base);
+int pscanf(const char *path, const char *fmt, ...);
diff --git a/slstatuso/util.o b/slstatuso/util.o
new file mode 100644
index 0000000..7cd4595
--- /dev/null
+++ b/slstatuso/util.o
Binary files differ
diff --git a/sto/FAQ b/sto/FAQ
new file mode 100755
index 0000000..6287a27
--- /dev/null
+++ b/sto/FAQ
@@ -0,0 +1,253 @@
+## Why does st not handle utmp entries?
+
+Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task.
+
+
+## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever!
+
+It means that st doesn’t have any terminfo entry on your system. Chances are
+you did not `make install`. If you just want to test it without installing it,
+you can manually run `tic -sx st.info`.
+
+
+## Nothing works, and nothing is said about an unknown terminal!
+
+* Some programs just assume they’re running in xterm i.e. they don’t rely on
+ terminfo. What you see is the current state of the “xterm compliance”.
+* Some programs don’t complain about the lacking st description and default to
+ another terminal. In that case see the question about terminfo.
+
+
+## How do I scroll back up?
+
+* Using a terminal multiplexer.
+ * `st -e tmux` using C-b [
+ * `st -e screen` using C-a ESC
+* Using the excellent tool of [scroll](https://git.suckless.org/scroll/).
+* Using the scrollback [patch](https://st.suckless.org/patches/scrollback/).
+
+
+## I would like to have utmp and/or scroll functionality by default
+
+You can add the absolute path of both programs in your config.h file. You only
+have to modify the value of utmp and scroll variables.
+
+
+## Why doesn't the Del key work in some programs?
+
+Taken from the terminfo manpage:
+
+ If the terminal has a keypad that transmits codes when the keys
+ are pressed, this information can be given. Note that it is not
+ possible to handle terminals where the keypad only works in
+ local (this applies, for example, to the unshifted HP 2621 keys).
+ If the keypad can be set to transmit or not transmit, give these
+ codes as smkx and rmkx. Otherwise the keypad is assumed to
+ always transmit.
+
+In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that
+applications which want to test against keypad keys send these
+sequences.
+
+But buggy applications (like bash and irssi, for example) don't do this. A fast
+solution for them is to use the following command:
+
+ $ printf '\033[?1h\033=' >/dev/tty
+
+or
+ $ tput smkx
+
+In the case of bash, readline is used. Readline has a different note in its
+manpage about this issue:
+
+ enable-keypad (Off)
+ When set to On, readline will try to enable the
+ application keypad when it is called. Some systems
+ need this to enable arrow keys.
+
+Adding this option to your .inputrc will fix the keypad problem for all
+applications using readline.
+
+If you are using zsh, then read the zsh FAQ
+<http://zsh.sourceforge.net/FAQ/zshfaq03.html#l25>:
+
+ It should be noted that the O / [ confusion can occur with other keys
+ such as Home and End. Some systems let you query the key sequences
+ sent by these keys from the system's terminal database, terminfo.
+ Unfortunately, the key sequences given there typically apply to the
+ mode that is not the one zsh uses by default (it's the "application"
+ mode rather than the "raw" mode). Explaining the use of terminfo is
+ outside of the scope of this FAQ, but if you wish to use the key
+ sequences given there you can tell the line editor to turn on
+ "application" mode when it starts and turn it off when it stops:
+
+ function zle-line-init () { echoti smkx }
+ function zle-line-finish () { echoti rmkx }
+ zle -N zle-line-init
+ zle -N zle-line-finish
+
+Putting these lines into your .zshrc will fix the problems.
+
+
+## How can I use meta in 8bit mode?
+
+St supports meta in 8bit mode, but the default terminfo entry doesn't
+use this capability. If you want it, you have to use the 'st-meta' value
+in TERM.
+
+
+## I cannot compile st in OpenBSD
+
+OpenBSD lacks librt, despite it being mandatory in POSIX
+<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html#tag_20_11_13>.
+If you want to compile st for OpenBSD you have to remove -lrt from config.mk, and
+st will compile without any loss of functionality, because all the functions are
+included in libc on this platform.
+
+
+## The Backspace Case
+
+St is emulating the Linux way of handling backspace being delete and delete being
+backspace.
+
+This is an issue that was discussed in suckless mailing list
+<https://lists.suckless.org/dev/1404/20697.html>. Here is why some old grumpy
+terminal users wants its backspace to be how he feels it:
+
+ Well, I am going to comment why I want to change the behaviour
+ of this key. When ASCII was defined in 1968, communication
+ with computers was done using punched cards, or hardcopy
+ terminals (basically a typewriter machine connected with the
+ computer using a serial port). ASCII defines DELETE as 7F,
+ because, in punched-card terms, it means all the holes of the
+ card punched; it is thus a kind of 'physical delete'. In the
+ same way, the BACKSPACE key was a non-destructive backspace,
+ as on a typewriter. So, if you wanted to delete a character,
+ you had to BACKSPACE and then DELETE. Another use of BACKSPACE
+ was to type accented characters, for example 'a BACKSPACE `'.
+ The VT100 had no BACKSPACE key; it was generated using the
+ CONTROL key as another control character (CONTROL key sets to
+ 0 b7 b6 b5, so it converts H (code 0x48) into BACKSPACE (code
+ 0x08)), but it had a DELETE key in a similar position where
+ the BACKSPACE key is located today on common PC keyboards.
+ All the terminal emulators emulated the difference between
+ these keys correctly: the backspace key generated a BACKSPACE
+ (^H) and delete key generated a DELETE (^?).
+
+ But a problem arose when Linus Torvalds wrote Linux. Unlike
+ earlier terminals, the Linux virtual terminal (the terminal
+ emulator integrated in the kernel) returned a DELETE when
+ backspace was pressed, due to the VT100 having a DELETE key in
+ the same position. This created a lot of problems (see [1]
+ and [2]). Since Linux has become the king, a lot of terminal
+ emulators today generate a DELETE when the backspace key is
+ pressed in order to avoid problems with Linux. The result is
+ that the only way of generating a BACKSPACE on these systems
+ is by using CONTROL + H. (I also think that emacs had an
+ important point here because the CONTROL + H prefix is used
+ in emacs in some commands (help commands).)
+
+ From point of view of the kernel, you can change the key
+ for deleting a previous character with stty erase. When you
+ connect a real terminal into a machine you describe the type
+ of terminal, so getty configures the correct value of stty
+ erase for this terminal. In the case of terminal emulators,
+ however, you don't have any getty that can set the correct
+ value of stty erase, so you always get the default value.
+ For this reason, it is necessary to add 'stty erase ^H' to your
+ profile if you have changed the value of the backspace key.
+ Of course, another solution is for st itself to modify the
+ value of stty erase. I usually have the inverse problem:
+ when I connect to non-Unix machines, I have to press CONTROL +
+ h to get a BACKSPACE. The inverse problem occurs when a user
+ connects to my Unix machines from a different system with a
+ correct backspace key.
+
+ [1] http://www.ibb.net/~anne/keyboard.html
+ [2] http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-5.html
+
+
+## But I really want the old grumpy behaviour of my terminal
+
+Apply [1].
+
+[1] https://st.suckless.org/patches/delkey
+
+
+## Why do images not work in st using the w3m image hack?
+
+w3mimg uses a hack that draws an image on top of the terminal emulator Drawable
+window. The hack relies on the terminal to use a single buffer to draw its
+contents directly.
+
+st uses double-buffered drawing so the image is quickly replaced and may show a
+short flicker effect.
+
+Below is a patch example to change st double-buffering to a single Drawable
+buffer.
+
+diff --git a/x.c b/x.c
+--- a/x.c
++++ b/x.c
+@@ -732,10 +732,6 @@ xresize(int col, int row)
+ win.tw = col * win.cw;
+ win.th = row * win.ch;
+
+- XFreePixmap(xw.dpy, xw.buf);
+- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
+- DefaultDepth(xw.dpy, xw.scr));
+- XftDrawChange(xw.draw, xw.buf);
+ xclear(0, 0, win.w, win.h);
+
+ /* resize to new width */
+@@ -1148,8 +1144,7 @@ xinit(int cols, int rows)
+ gcvalues.graphics_exposures = False;
+ dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
+ &gcvalues);
+- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
+- DefaultDepth(xw.dpy, xw.scr));
++ xw.buf = xw.win;
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
+
+@@ -1632,8 +1627,6 @@ xdrawline(Line line, int x1, int y1, int x2)
+ void
+ xfinishdraw(void)
+ {
+- XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w,
+- win.h, 0, 0);
+ XSetForeground(xw.dpy, dc.gc,
+ dc.col[IS_SET(MODE_REVERSE)?
+ defaultfg : defaultbg].pixel);
+
+
+## BadLength X error in Xft when trying to render emoji
+
+Xft makes st crash when rendering color emojis with the following error:
+
+"X Error of failed request: BadLength (poly request too large or internal Xlib length error)"
+ Major opcode of failed request: 139 (RENDER)
+ Minor opcode of failed request: 20 (RenderAddGlyphs)
+ Serial number of failed request: 1595
+ Current serial number in output stream: 1818"
+
+This is a known bug in Xft (not st) which happens on some platforms and
+combination of particular fonts and fontconfig settings.
+
+See also:
+https://gitlab.freedesktop.org/xorg/lib/libxft/issues/6
+https://bugs.freedesktop.org/show_bug.cgi?id=107534
+https://bugzilla.redhat.com/show_bug.cgi?id=1498269
+
+The solution is to remove color emoji fonts or disable this in the fontconfig
+XML configuration. As an ugly workaround (which may work only on newer
+fontconfig versions (FC_COLOR)), the following code can be used to mask color
+fonts:
+
+ FcPatternAddBool(fcpattern, FC_COLOR, FcFalse);
+
+Please don't bother reporting this bug to st, but notify the upstream Xft
+developers about fixing this bug.
+
+As of 2022-09-05 this now seems to be finally fixed in libXft 2.3.5:
+https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS
diff --git a/sto/LEGACY b/sto/LEGACY
new file mode 100755
index 0000000..bf28b1e
--- /dev/null
+++ b/sto/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/sto/LICENSE b/sto/LICENSE
new file mode 100755
index 0000000..3cbf420
--- /dev/null
+++ b/sto/LICENSE
@@ -0,0 +1,34 @@
+MIT/X Consortium License
+
+© 2014-2022 Hiltjo Posthuma <hiltjo at codemadness dot org>
+© 2018 Devin J. Pohly <djpohly at gmail dot com>
+© 2014-2017 Quentin Rameau <quinq at fifth dot space>
+© 2009-2012 Aurélien APTEL <aurelien dot aptel at gmail dot com>
+© 2008-2017 Anselm R Garbe <garbeam at gmail dot com>
+© 2012-2017 Roberto E. Vargas Caballero <k0ga at shike2 dot com>
+© 2012-2016 Christoph Lohmann <20h at r-36 dot net>
+© 2013 Eon S. Jeon <esjeon at hyunmu dot am>
+© 2013 Alexander Sedov <alex0player at gmail dot com>
+© 2013 Mark Edgar <medgar123 at gmail dot com>
+© 2013-2014 Eric Pruitt <eric.pruitt at gmail dot com>
+© 2013 Michael Forney <mforney at mforney dot org>
+© 2013-2014 Markus Teich <markus dot teich at stusta dot mhn dot de>
+© 2014-2015 Laslo Hunhold <dev at frign dot de>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/sto/Makefile b/sto/Makefile
new file mode 100755
index 0000000..470ac86
--- /dev/null
+++ b/sto/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/sto/README b/sto/README
new file mode 100755
index 0000000..c169ed2
--- /dev/null
+++ b/sto/README
@@ -0,0 +1,35 @@
+sto - simple terminal for openbsd
+---------------------------------
+
+st is a simple terminal emulator for X which sucks less.
+
+
+Requirements
+------------
+In order to build st you need the Xlib header files.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (st is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install st (if
+necessary as root):
+
+ make clean install
+
+
+Running st
+----------
+If you did not install st with make clean install, you must compile
+the st terminfo entry with the following command:
+
+ tic -sx st.info
+
+See the man page for additional details.
+
+Credits
+-------
+Based on Aurélien APTEL <aurelien dot aptel at gmail dot com> bt source code.
+
diff --git a/sto/TODO b/sto/TODO
new file mode 100755
index 0000000..5f74cd5
--- /dev/null
+++ b/sto/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/sto/arg.h b/sto/arg.h
new file mode 100755
index 0000000..a22e019
--- /dev/null
+++ b/sto/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/sto/config.def.h b/sto/config.def.h
new file mode 100755
index 0000000..3d8f276
--- /dev/null
+++ b/sto/config.def.h
@@ -0,0 +1,476 @@
+/* See LICENSE file for copyright and license details. */
+
+/*
+ * appearance
+ *
+ * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
+ */
+static char *font = "JetBrains Mono:pixelsize=8:antialias=true:autohint=true";
+static int borderpx = 4;
+
+/*
+ * 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;
+
+/* Terminal colors (16 first used in escape sequence) */
+static const char *colorname[] = {
+ /* 8 normal colors */
+ "black",
+ "red3",
+ "green3",
+ "yellow3",
+ "blue2",
+ "magenta3",
+ "cyan3",
+ "gray90",
+
+ /* 8 bright colors */
+ "gray50",
+ "red",
+ "green",
+ "yellow",
+ "#5c5cff",
+ "magenta",
+ "cyan",
+ "white",
+
+ [255] = 0,
+
+ /* more colors can be added after 255 to use with DefaultXX */
+ "#cccccc",
+ "#555555",
+ "gray90", /* default foreground colour */
+ "black", /* default background colour */
+};
+
+
+/*
+ * Default colors (colorname index)
+ * foreground, background, cursor, reverse cursor
+ */
+unsigned int defaultfg = 258;
+unsigned int defaultbg = 259;
+unsigned int defaultcs = 256;
+static unsigned int defaultrcs = 257;
+
+/*
+ * Default shape of cursor
+ * 2: Block ("█")
+ * 4: Underline ("_")
+ * 6: Bar ("|")
+ * 7: Snowman ("☃")
+ */
+static unsigned int cursorshape = 2;
+
+/*
+ * Default columns and rows numbers
+ */
+
+static unsigned int cols = 80;
+static unsigned int rows = 24;
+
+/*
+ * Default colour and shape of the mouse cursor
+ */
+static unsigned int mouseshape = XC_xterm;
+static unsigned int mousefg = 7;
+static unsigned int mousebg = 0;
+
+/*
+ * Color used to display font attributes when fontconfig selected a font which
+ * doesn't match the ones requested.
+ */
+static unsigned int defaultattr = 11;
+
+/*
+ * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set).
+ * Note that if you want to use ShiftMask with selmasks, set this to an other
+ * modifier, set to 0 to not use it.
+ */
+static uint forcemousemod = ShiftMask;
+
+/*
+ * Internal mouse shortcuts.
+ * Beware that overloading Button1 will disable the selection.
+ */
+static MouseShortcut mshortcuts[] = {
+ /* mask button function argument release */
+ { XK_ANY_MOD, 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/sto/config.h b/sto/config.h
new file mode 100755
index 0000000..1f1cee0
--- /dev/null
+++ b/sto/config.h
@@ -0,0 +1,476 @@
+/* See LICENSE file for copyright and license details. */
+
+/*
+ * appearance
+ *
+ * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
+ */
+static char *font = "Terminus:pixelsize=14:antialias=true:autohint=true";
+static int borderpx = 4;
+
+/*
+ * 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;
+
+/* Terminal colors (16 first used in escape sequence) */
+static const char *colorname[] = {
+ /* 8 normal colors */
+ "black",
+ "red3",
+ "green3",
+ "yellow3",
+ "blue2",
+ "magenta3",
+ "cyan3",
+ "gray90",
+
+ /* 8 bright colors */
+ "gray50",
+ "red",
+ "green",
+ "yellow",
+ "#5c5cff",
+ "magenta",
+ "cyan",
+ "white",
+
+ [255] = 0,
+
+ /* more colors can be added after 255 to use with DefaultXX */
+ "#cccccc",
+ "#555555",
+ "gray90", /* default foreground colour */
+ "black", /* default background colour */
+};
+
+
+/*
+ * Default colors (colorname index)
+ * foreground, background, cursor, reverse cursor
+ */
+unsigned int defaultfg = 258;
+unsigned int defaultbg = 259;
+unsigned int defaultcs = 256;
+static unsigned int defaultrcs = 257;
+
+/*
+ * Default shape of cursor
+ * 2: Block ("█")
+ * 4: Underline ("_")
+ * 6: Bar ("|")
+ * 7: Snowman ("☃")
+ */
+static unsigned int cursorshape = 2;
+
+/*
+ * Default columns and rows numbers
+ */
+
+static unsigned int cols = 80;
+static unsigned int rows = 24;
+
+/*
+ * Default colour and shape of the mouse cursor
+ */
+static unsigned int mouseshape = XC_xterm;
+static unsigned int mousefg = 7;
+static unsigned int mousebg = 0;
+
+/*
+ * Color used to display font attributes when fontconfig selected a font which
+ * doesn't match the ones requested.
+ */
+static unsigned int defaultattr = 11;
+
+/*
+ * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set).
+ * Note that if you want to use ShiftMask with selmasks, set this to an other
+ * modifier, set to 0 to not use it.
+ */
+static uint forcemousemod = ShiftMask;
+
+/*
+ * Internal mouse shortcuts.
+ * Beware that overloading Button1 will disable the selection.
+ */
+static MouseShortcut mshortcuts[] = {
+ /* mask button function argument release */
+ { XK_ANY_MOD, 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/sto/config.mk b/sto/config.mk
new file mode 100755
index 0000000..cc8f699
--- /dev/null
+++ b/sto/config.mk
@@ -0,0 +1,36 @@
+# st version
+VERSION = 0.9
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+
+PKG_CONFIG = pkg-config
+
+# includes and libs
+INCS = -I$(X11INC) \
+ `$(PKG_CONFIG) --cflags fontconfig` \
+ `$(PKG_CONFIG) --cflags freetype2`
+LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
+ `$(PKG_CONFIG) --libs fontconfig` \
+ `$(PKG_CONFIG) --libs freetype2`
+
+# flags
+STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
+STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
+STLDFLAGS = $(LIBS) $(LDFLAGS)
+
+# OpenBSD:
+CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
+LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
+ `$(PKG_CONFIG) --libs fontconfig` \
+ `$(PKG_CONFIG) --libs freetype2`
+MANPREFIX = ${PREFIX}/man
+
+# compiler and linker
+# CC = c99
diff --git a/sto/patches/st-scrollback-0.8.5.diff b/sto/patches/st-scrollback-0.8.5.diff
new file mode 100755
index 0000000..750111d
--- /dev/null
+++ b/sto/patches/st-scrollback-0.8.5.diff
@@ -0,0 +1,350 @@
+diff --git a/config.def.h b/config.def.h
+index 91ab8ca..e3b469b 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -201,6 +201,8 @@ static Shortcut shortcuts[] = {
+ { TERMMOD, XK_Y, selpaste, {.i = 0} },
+ { ShiftMask, XK_Insert, selpaste, {.i = 0} },
+ { TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
++ { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
++ { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
+ };
+
+ /*
+diff --git a/st.c b/st.c
+index 51049ba..cd750f2 100644
+--- a/st.c
++++ b/st.c
+@@ -35,6 +35,7 @@
+ #define ESC_ARG_SIZ 16
+ #define STR_BUF_SIZ ESC_BUF_SIZ
+ #define STR_ARG_SIZ ESC_ARG_SIZ
++#define HISTSIZE 2000
+
+ /* macros */
+ #define IS_SET(flag) ((term.mode & (flag)) != 0)
+@@ -42,6 +43,9 @@
+ #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
+ #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
+ #define ISDELIM(u) (u && wcschr(worddelimiters, u))
++#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \
++ term.scr + HISTSIZE + 1) % HISTSIZE] : \
++ term.line[(y) - term.scr])
+
+ enum term_mode {
+ MODE_WRAP = 1 << 0,
+@@ -115,6 +119,9 @@ typedef struct {
+ int col; /* nb col */
+ Line *line; /* screen */
+ Line *alt; /* alternate screen */
++ Line hist[HISTSIZE]; /* history buffer */
++ int histi; /* history index */
++ int scr; /* scroll back */
+ int *dirty; /* dirtyness of lines */
+ TCursor c; /* cursor */
+ int ocx; /* old cursor col */
+@@ -184,8 +191,8 @@ static void tnewline(int);
+ static void tputtab(int);
+ static void tputc(Rune);
+ static void treset(void);
+-static void tscrollup(int, int);
+-static void tscrolldown(int, int);
++static void tscrollup(int, int, int);
++static void tscrolldown(int, int, int);
+ static void tsetattr(const int *, int);
+ static void tsetchar(Rune, const Glyph *, int, int);
+ static void tsetdirt(int, int);
+@@ -416,10 +423,10 @@ tlinelen(int y)
+ {
+ int i = term.col;
+
+- if (term.line[y][i - 1].mode & ATTR_WRAP)
++ if (TLINE(y)[i - 1].mode & ATTR_WRAP)
+ return i;
+
+- while (i > 0 && term.line[y][i - 1].u == ' ')
++ while (i > 0 && TLINE(y)[i - 1].u == ' ')
+ --i;
+
+ return i;
+@@ -528,7 +535,7 @@ selsnap(int *x, int *y, int direction)
+ * Snap around if the word wraps around at the end or
+ * beginning of a line.
+ */
+- prevgp = &term.line[*y][*x];
++ prevgp = &TLINE(*y)[*x];
+ prevdelim = ISDELIM(prevgp->u);
+ for (;;) {
+ newx = *x + direction;
+@@ -543,14 +550,14 @@ selsnap(int *x, int *y, int direction)
+ yt = *y, xt = *x;
+ else
+ yt = newy, xt = newx;
+- if (!(term.line[yt][xt].mode & ATTR_WRAP))
++ if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
+ break;
+ }
+
+ if (newx >= tlinelen(newy))
+ break;
+
+- gp = &term.line[newy][newx];
++ gp = &TLINE(newy)[newx];
+ delim = ISDELIM(gp->u);
+ if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
+ || (delim && gp->u != prevgp->u)))
+@@ -571,14 +578,14 @@ selsnap(int *x, int *y, int direction)
+ *x = (direction < 0) ? 0 : term.col - 1;
+ if (direction < 0) {
+ for (; *y > 0; *y += direction) {
+- if (!(term.line[*y-1][term.col-1].mode
++ if (!(TLINE(*y-1)[term.col-1].mode
+ & ATTR_WRAP)) {
+ break;
+ }
+ }
+ } else if (direction > 0) {
+ for (; *y < term.row-1; *y += direction) {
+- if (!(term.line[*y][term.col-1].mode
++ if (!(TLINE(*y)[term.col-1].mode
+ & ATTR_WRAP)) {
+ break;
+ }
+@@ -609,13 +616,13 @@ getsel(void)
+ }
+
+ if (sel.type == SEL_RECTANGULAR) {
+- gp = &term.line[y][sel.nb.x];
++ gp = &TLINE(y)[sel.nb.x];
+ lastx = sel.ne.x;
+ } else {
+- gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
++ gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
+ lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
+ }
+- last = &term.line[y][MIN(lastx, linelen-1)];
++ last = &TLINE(y)[MIN(lastx, linelen-1)];
+ while (last >= gp && last->u == ' ')
+ --last;
+
+@@ -851,6 +858,9 @@ void
+ ttywrite(const char *s, size_t n, int may_echo)
+ {
+ const char *next;
++ Arg arg = (Arg) { .i = term.scr };
++
++ kscrolldown(&arg);
+
+ if (may_echo && IS_SET(MODE_ECHO))
+ twrite(s, n, 1);
+@@ -1062,12 +1072,52 @@ tswapscreen(void)
+ }
+
+ void
+-tscrolldown(int orig, int n)
++kscrolldown(const Arg* a)
++{
++ int n = a->i;
++
++ if (n < 0)
++ n = term.row + n;
++
++ if (n > term.scr)
++ n = term.scr;
++
++ if (term.scr > 0) {
++ term.scr -= n;
++ selscroll(0, -n);
++ tfulldirt();
++ }
++}
++
++void
++kscrollup(const Arg* a)
++{
++ int n = a->i;
++
++ if (n < 0)
++ n = term.row + n;
++
++ if (term.scr <= HISTSIZE-n) {
++ term.scr += n;
++ selscroll(0, n);
++ tfulldirt();
++ }
++}
++
++void
++tscrolldown(int orig, int n, int copyhist)
+ {
+ int i;
+ Line temp;
+
+ LIMIT(n, 0, term.bot-orig+1);
++ if (copyhist) {
++ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
++ temp = term.hist[term.histi];
++ term.hist[term.histi] = term.line[term.bot];
++ term.line[term.bot] = temp;
++ }
++
+
+ tsetdirt(orig, term.bot-n);
+ tclearregion(0, term.bot-n+1, term.col-1, term.bot);
+@@ -1078,17 +1128,28 @@ tscrolldown(int orig, int n)
+ term.line[i-n] = temp;
+ }
+
+- selscroll(orig, n);
++ if (term.scr == 0)
++ selscroll(orig, n);
+ }
+
+ void
+-tscrollup(int orig, int n)
++tscrollup(int orig, int n, int copyhist)
+ {
+ int i;
+ Line temp;
+
+ LIMIT(n, 0, term.bot-orig+1);
+
++ if (copyhist) {
++ term.histi = (term.histi + 1) % HISTSIZE;
++ temp = term.hist[term.histi];
++ term.hist[term.histi] = term.line[orig];
++ term.line[orig] = temp;
++ }
++
++ if (term.scr > 0 && term.scr < HISTSIZE)
++ term.scr = MIN(term.scr + n, HISTSIZE-1);
++
+ tclearregion(0, orig, term.col-1, orig+n-1);
+ tsetdirt(orig+n, term.bot);
+
+@@ -1098,7 +1159,8 @@ tscrollup(int orig, int n)
+ term.line[i+n] = temp;
+ }
+
+- selscroll(orig, -n);
++ if (term.scr == 0)
++ selscroll(orig, -n);
+ }
+
+ void
+@@ -1127,7 +1189,7 @@ tnewline(int first_col)
+ int y = term.c.y;
+
+ if (y == term.bot) {
+- tscrollup(term.top, 1);
++ tscrollup(term.top, 1, 1);
+ } else {
+ y++;
+ }
+@@ -1292,14 +1354,14 @@ void
+ tinsertblankline(int n)
+ {
+ if (BETWEEN(term.c.y, term.top, term.bot))
+- tscrolldown(term.c.y, n);
++ tscrolldown(term.c.y, n, 0);
+ }
+
+ void
+ tdeleteline(int n)
+ {
+ if (BETWEEN(term.c.y, term.top, term.bot))
+- tscrollup(term.c.y, n);
++ tscrollup(term.c.y, n, 0);
+ }
+
+ int32_t
+@@ -1736,11 +1798,11 @@ csihandle(void)
+ break;
+ case 'S': /* SU -- Scroll <n> line up */
+ DEFAULT(csiescseq.arg[0], 1);
+- tscrollup(term.top, csiescseq.arg[0]);
++ tscrollup(term.top, csiescseq.arg[0], 0);
+ break;
+ case 'T': /* SD -- Scroll <n> line down */
+ DEFAULT(csiescseq.arg[0], 1);
+- tscrolldown(term.top, csiescseq.arg[0]);
++ tscrolldown(term.top, csiescseq.arg[0], 0);
+ break;
+ case 'L': /* IL -- Insert <n> blank lines */
+ DEFAULT(csiescseq.arg[0], 1);
+@@ -2330,7 +2392,7 @@ eschandle(uchar ascii)
+ return 0;
+ case 'D': /* IND -- Linefeed */
+ if (term.c.y == term.bot) {
+- tscrollup(term.top, 1);
++ tscrollup(term.top, 1, 1);
+ } else {
+ tmoveto(term.c.x, term.c.y+1);
+ }
+@@ -2343,7 +2405,7 @@ eschandle(uchar ascii)
+ break;
+ case 'M': /* RI -- Reverse index */
+ if (term.c.y == term.top) {
+- tscrolldown(term.top, 1);
++ tscrolldown(term.top, 1, 1);
+ } else {
+ tmoveto(term.c.x, term.c.y-1);
+ }
+@@ -2557,7 +2619,7 @@ twrite(const char *buf, int buflen, int show_ctrl)
+ void
+ tresize(int col, int row)
+ {
+- int i;
++ int i, j;
+ int minrow = MIN(row, term.row);
+ int mincol = MIN(col, term.col);
+ int *bp;
+@@ -2594,6 +2656,14 @@ tresize(int col, int row)
+ term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
+ term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
+
++ for (i = 0; i < HISTSIZE; i++) {
++ term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
++ for (j = mincol; j < col; j++) {
++ term.hist[i][j] = term.c.attr;
++ term.hist[i][j].u = ' ';
++ }
++ }
++
+ /* resize each row to new width, zero-pad if needed */
+ for (i = 0; i < minrow; i++) {
+ term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
+@@ -2652,7 +2722,7 @@ drawregion(int x1, int y1, int x2, int y2)
+ continue;
+
+ term.dirty[y] = 0;
+- xdrawline(term.line[y], x1, y, x2);
++ xdrawline(TLINE(y), x1, y, x2);
+ }
+ }
+
+@@ -2673,8 +2743,9 @@ draw(void)
+ cx--;
+
+ drawregion(0, 0, term.col, term.row);
+- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
+- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
++ if (term.scr == 0)
++ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
++ term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
+ term.ocx = cx;
+ term.ocy = term.c.y;
+ xfinishdraw();
+diff --git a/st.h b/st.h
+index 519b9bd..da36b34 100644
+--- a/st.h
++++ b/st.h
+@@ -81,6 +81,8 @@ void die(const char *, ...);
+ void redraw(void);
+ void draw(void);
+
++void kscrolldown(const Arg *);
++void kscrollup(const Arg *);
+ void printscreen(const Arg *);
+ void printsel(const Arg *);
+ void sendbreak(const Arg *);
diff --git a/sto/st b/sto/st
new file mode 100755
index 0000000..09682fc
--- /dev/null
+++ b/sto/st
Binary files differ
diff --git a/sto/st.1 b/sto/st.1
new file mode 100755
index 0000000..39120b4
--- /dev/null
+++ b/sto/st.1
@@ -0,0 +1,177 @@
+.TH ST 1 st\-VERSION
+.SH NAME
+st \- simple terminal
+.SH SYNOPSIS
+.B st
+.RB [ \-aiv ]
+.RB [ \-c
+.IR class ]
+.RB [ \-f
+.IR font ]
+.RB [ \-g
+.IR geometry ]
+.RB [ \-n
+.IR name ]
+.RB [ \-o
+.IR iofile ]
+.RB [ \-T
+.IR title ]
+.RB [ \-t
+.IR title ]
+.RB [ \-l
+.IR line ]
+.RB [ \-w
+.IR windowid ]
+.RB [[ \-e ]
+.IR command
+.RI [ arguments ...]]
+.PP
+.B st
+.RB [ \-aiv ]
+.RB [ \-c
+.IR class ]
+.RB [ \-f
+.IR font ]
+.RB [ \-g
+.IR geometry ]
+.RB [ \-n
+.IR name ]
+.RB [ \-o
+.IR iofile ]
+.RB [ \-T
+.IR title ]
+.RB [ \-t
+.IR title ]
+.RB [ \-w
+.IR windowid ]
+.RB \-l
+.IR line
+.RI [ stty_args ...]
+.SH DESCRIPTION
+.B st
+is a simple terminal emulator.
+.SH OPTIONS
+.TP
+.B \-a
+disable alternate screens in terminal
+.TP
+.BI \-c " class"
+defines the window class (default $TERM).
+.TP
+.BI \-f " font"
+defines the
+.I font
+to use when st is run.
+.TP
+.BI \-g " geometry"
+defines the X11 geometry string.
+The form is [=][<cols>{xX}<rows>][{+-}<xoffset>{+-}<yoffset>]. See
+.BR XParseGeometry (3)
+for further details.
+.TP
+.B \-i
+will fixate the position given with the -g option.
+.TP
+.BI \-n " name"
+defines the window instance name (default $TERM).
+.TP
+.BI \-o " iofile"
+writes all the I/O to
+.I iofile.
+This feature is useful when recording st sessions. A value of "-" means
+standard output.
+.TP
+.BI \-T " title"
+defines the window title (default 'st').
+.TP
+.BI \-t " title"
+defines the window title (default 'st').
+.TP
+.BI \-w " windowid"
+embeds st within the window identified by
+.I windowid
+.TP
+.BI \-l " line"
+use a tty
+.I line
+instead of a pseudo terminal.
+.I line
+should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port
+0).
+When this flag is given
+remaining arguments are used as flags for
+.BR stty(1).
+By default st initializes the serial line to 8 bits, no parity, 1 stop bit
+and a 38400 baud rate. The speed is set by appending it as last argument
+(e.g. 'st -l /dev/ttyS0 115200'). Arguments before the last one are
+.BR stty(1)
+flags. If you want to set odd parity on 115200 baud use for example 'st -l
+/dev/ttyS0 parenb parodd 115200'. Set the number of bits by using for
+example 'st -l /dev/ttyS0 cs7 115200'. See
+.BR stty(1)
+for more arguments and cases.
+.TP
+.B \-v
+prints version information to stderr, then exits.
+.TP
+.BI \-e " command " [ " arguments " "... ]"
+st executes
+.I command
+instead of the shell. If this is used it
+.B must be the last option
+on the command line, as in xterm / rxvt.
+This option is only intended for compatibility,
+and all the remaining arguments are used as a command
+even without it.
+.SH SHORTCUTS
+.TP
+.B Break
+Send a break in the serial line.
+Break key is obtained in PC keyboards
+pressing at the same time control and pause.
+.TP
+.B Ctrl-Print Screen
+Toggle if st should print to the
+.I iofile.
+.TP
+.B Shift-Print Screen
+Print the full screen to the
+.I iofile.
+.TP
+.B Print Screen
+Print the selection to the
+.I iofile.
+.TP
+.B Ctrl-Shift-Page Up
+Increase font size.
+.TP
+.B Ctrl-Shift-Page Down
+Decrease font size.
+.TP
+.B Ctrl-Shift-Home
+Reset to default font size.
+.TP
+.B Ctrl-Shift-y
+Paste from primary selection (middle mouse button).
+.TP
+.B Ctrl-Shift-c
+Copy the selected text to the clipboard selection.
+.TP
+.B Ctrl-Shift-v
+Paste from the clipboard selection.
+.SH CUSTOMIZATION
+.B st
+can be customized by creating a custom config.h and (re)compiling the source
+code. This keeps it fast, secure and simple.
+.SH AUTHORS
+See the LICENSE file for the authors.
+.SH LICENSE
+See the LICENSE file for the terms of redistribution.
+.SH SEE ALSO
+.BR tabbed (1),
+.BR utmp (1),
+.BR stty (1),
+.BR scroll (1)
+.SH BUGS
+See the TODO file in the distribution.
+
diff --git a/sto/st.c b/sto/st.c
new file mode 100755
index 0000000..79ee9ba
--- /dev/null
+++ b/sto/st.c
@@ -0,0 +1,2727 @@
+/* See LICENSE for license details. */
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "st.h"
+#include "win.h"
+
+#if defined(__linux)
+ #include <pty.h>
+#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+ #include <util.h>
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+ #include <libutil.h>
+#endif
+
+/* Arbitrary sizes */
+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+#define ESC_BUF_SIZ (128*UTF_SIZ)
+#define ESC_ARG_SIZ 16
+#define STR_BUF_SIZ ESC_BUF_SIZ
+#define STR_ARG_SIZ ESC_ARG_SIZ
+#define HISTSIZE 2000
+
+/* macros */
+#define IS_SET(flag) ((term.mode & (flag)) != 0)
+#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
+#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
+#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
+#define ISDELIM(u) (u && wcschr(worddelimiters, u))
+#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \
+ term.scr + HISTSIZE + 1) % HISTSIZE] : \
+ term.line[(y) - term.scr])
+
+enum term_mode {
+ MODE_WRAP = 1 << 0,
+ MODE_INSERT = 1 << 1,
+ MODE_ALTSCREEN = 1 << 2,
+ MODE_CRLF = 1 << 3,
+ MODE_ECHO = 1 << 4,
+ MODE_PRINT = 1 << 5,
+ MODE_UTF8 = 1 << 6,
+};
+
+enum cursor_movement {
+ CURSOR_SAVE,
+ CURSOR_LOAD
+};
+
+enum cursor_state {
+ CURSOR_DEFAULT = 0,
+ CURSOR_WRAPNEXT = 1,
+ CURSOR_ORIGIN = 2
+};
+
+enum charset {
+ CS_GRAPHIC0,
+ CS_GRAPHIC1,
+ CS_UK,
+ CS_USA,
+ CS_MULTI,
+ CS_GER,
+ CS_FIN
+};
+
+enum escape_state {
+ ESC_START = 1,
+ ESC_CSI = 2,
+ ESC_STR = 4, /* DCS, OSC, PM, APC */
+ ESC_ALTCHARSET = 8,
+ ESC_STR_END = 16, /* a final string was encountered */
+ ESC_TEST = 32, /* Enter in test mode */
+ ESC_UTF8 = 64,
+};
+
+typedef struct {
+ Glyph attr; /* current char attributes */
+ int x;
+ int y;
+ char state;
+} TCursor;
+
+typedef struct {
+ int mode;
+ int type;
+ int snap;
+ /*
+ * Selection variables:
+ * nb – normalized coordinates of the beginning of the selection
+ * ne – normalized coordinates of the end of the selection
+ * ob – original coordinates of the beginning of the selection
+ * oe – original coordinates of the end of the selection
+ */
+ struct {
+ int x, y;
+ } nb, ne, ob, oe;
+
+ int alt;
+} Selection;
+
+/* Internal representation of the screen */
+typedef struct {
+ int row; /* nb row */
+ int col; /* nb col */
+ Line *line; /* screen */
+ Line *alt; /* alternate screen */
+ Line hist[HISTSIZE]; /* history buffer */
+ int histi; /* history index */
+ int scr; /* scroll back */
+ int *dirty; /* dirtyness of lines */
+ TCursor c; /* cursor */
+ int ocx; /* old cursor col */
+ int ocy; /* old cursor row */
+ int top; /* top scroll limit */
+ int bot; /* bottom scroll limit */
+ int mode; /* terminal mode flags */
+ int esc; /* escape state flags */
+ char trantbl[4]; /* charset table translation */
+ int charset; /* current charset */
+ int icharset; /* selected charset for sequence */
+ int *tabs;
+ Rune lastc; /* last printed char outside of sequence, 0 if control */
+} Term;
+
+/* CSI Escape sequence structs */
+/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
+typedef struct {
+ char buf[ESC_BUF_SIZ]; /* raw string */
+ size_t len; /* raw string length */
+ char priv;
+ int arg[ESC_ARG_SIZ];
+ int narg; /* nb of args */
+ char mode[2];
+} CSIEscape;
+
+/* STR Escape sequence structs */
+/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
+typedef struct {
+ char type; /* ESC type ... */
+ char *buf; /* allocated raw string */
+ size_t siz; /* allocation size */
+ size_t len; /* raw string length */
+ char *args[STR_ARG_SIZ];
+ int narg; /* nb of args */
+} STREscape;
+
+static void execsh(char *, char **);
+static void stty(char **);
+static void sigchld(int);
+static void ttywriteraw(const char *, size_t);
+
+static void csidump(void);
+static void csihandle(void);
+static void csiparse(void);
+static void csireset(void);
+static 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 <n> blank char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tinsertblank(csiescseq.arg[0]);
+ break;
+ case 'A': /* CUU -- Cursor <n> Up */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
+ break;
+ case 'B': /* CUD -- Cursor <n> Down */
+ case 'e': /* VPR --Cursor <n> Down */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
+ break;
+ case 'i': /* MC -- Media Copy */
+ switch (csiescseq.arg[0]) {
+ case 0:
+ tdump();
+ break;
+ case 1:
+ tdumpline(term.c.y);
+ break;
+ case 2:
+ tdumpsel();
+ break;
+ case 4:
+ term.mode &= ~MODE_PRINT;
+ break;
+ case 5:
+ term.mode |= MODE_PRINT;
+ break;
+ }
+ break;
+ case 'c': /* DA -- Device Attributes */
+ if (csiescseq.arg[0] == 0)
+ ttywrite(vtiden, strlen(vtiden), 0);
+ break;
+ case 'b': /* REP -- if last char is printable print it <n> more times */
+ DEFAULT(csiescseq.arg[0], 1);
+ if (term.lastc)
+ while (csiescseq.arg[0]-- > 0)
+ tputc(term.lastc);
+ break;
+ case 'C': /* CUF -- Cursor <n> Forward */
+ case 'a': /* HPR -- Cursor <n> Forward */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
+ break;
+ case 'D': /* CUB -- Cursor <n> Backward */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
+ break;
+ case 'E': /* CNL -- Cursor <n> Down and first col */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(0, term.c.y+csiescseq.arg[0]);
+ break;
+ case 'F': /* CPL -- Cursor <n> Up and first col */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(0, term.c.y-csiescseq.arg[0]);
+ break;
+ case 'g': /* TBC -- Tabulation clear */
+ switch (csiescseq.arg[0]) {
+ case 0: /* clear current tab stop */
+ term.tabs[term.c.x] = 0;
+ break;
+ case 3: /* clear all the tabs */
+ memset(term.tabs, 0, term.col * sizeof(*term.tabs));
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 'G': /* CHA -- Move to <col> */
+ case '`': /* HPA */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(csiescseq.arg[0]-1, term.c.y);
+ break;
+ case 'H': /* CUP -- Move to <row> <col> */
+ case 'f': /* HVP */
+ DEFAULT(csiescseq.arg[0], 1);
+ DEFAULT(csiescseq.arg[1], 1);
+ tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
+ break;
+ case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
+ DEFAULT(csiescseq.arg[0], 1);
+ tputtab(csiescseq.arg[0]);
+ break;
+ case 'J': /* ED -- Clear screen */
+ switch (csiescseq.arg[0]) {
+ case 0: /* below */
+ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
+ if (term.c.y < term.row-1) {
+ tclearregion(0, term.c.y+1, term.col-1,
+ term.row-1);
+ }
+ break;
+ case 1: /* above */
+ if (term.c.y > 1)
+ tclearregion(0, 0, term.col-1, term.c.y-1);
+ tclearregion(0, term.c.y, term.c.x, term.c.y);
+ break;
+ case 2: /* all */
+ tclearregion(0, 0, term.col-1, term.row-1);
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 'K': /* EL -- Clear line */
+ switch (csiescseq.arg[0]) {
+ case 0: /* right */
+ tclearregion(term.c.x, term.c.y, term.col-1,
+ term.c.y);
+ break;
+ case 1: /* left */
+ tclearregion(0, term.c.y, term.c.x, term.c.y);
+ break;
+ case 2: /* all */
+ tclearregion(0, term.c.y, term.col-1, term.c.y);
+ break;
+ }
+ break;
+ case 'S': /* SU -- Scroll <n> line up */
+ DEFAULT(csiescseq.arg[0], 1);
+ tscrollup(term.top, csiescseq.arg[0], 0);
+ break;
+ case 'T': /* SD -- Scroll <n> line down */
+ DEFAULT(csiescseq.arg[0], 1);
+ tscrolldown(term.top, csiescseq.arg[0], 0);
+ break;
+ case 'L': /* IL -- Insert <n> blank lines */
+ DEFAULT(csiescseq.arg[0], 1);
+ tinsertblankline(csiescseq.arg[0]);
+ break;
+ case 'l': /* RM -- Reset Mode */
+ tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
+ break;
+ case 'M': /* DL -- Delete <n> lines */
+ DEFAULT(csiescseq.arg[0], 1);
+ tdeleteline(csiescseq.arg[0]);
+ break;
+ case 'X': /* ECH -- Erase <n> char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tclearregion(term.c.x, term.c.y,
+ term.c.x + csiescseq.arg[0] - 1, term.c.y);
+ break;
+ case 'P': /* DCH -- Delete <n> char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tdeletechar(csiescseq.arg[0]);
+ break;
+ case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
+ DEFAULT(csiescseq.arg[0], 1);
+ tputtab(-csiescseq.arg[0]);
+ break;
+ case 'd': /* VPA -- Move to <row> */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveato(term.c.x, csiescseq.arg[0]-1);
+ break;
+ case 'h': /* SM -- Set terminal mode */
+ tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
+ break;
+ case 'm': /* SGR -- Terminal attribute (color) */
+ tsetattr(csiescseq.arg, csiescseq.narg);
+ break;
+ case 'n': /* DSR – Device Status Report (cursor position) */
+ if (csiescseq.arg[0] == 6) {
+ len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
+ term.c.y+1, term.c.x+1);
+ ttywrite(buf, len, 0);
+ }
+ break;
+ case 'r': /* DECSTBM -- Set Scrolling Region */
+ if (csiescseq.priv) {
+ goto unknown;
+ } else {
+ DEFAULT(csiescseq.arg[0], 1);
+ DEFAULT(csiescseq.arg[1], term.row);
+ tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
+ tmoveato(0, 0);
+ }
+ break;
+ case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
+ tcursor(CURSOR_SAVE);
+ break;
+ case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
+ tcursor(CURSOR_LOAD);
+ break;
+ case ' ':
+ switch (csiescseq.mode[1]) {
+ case 'q': /* DECSCUSR -- Set Cursor Style */
+ if (xsetcursor(csiescseq.arg[0]))
+ goto unknown;
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ }
+}
+
+void
+csidump(void)
+{
+ size_t i;
+ uint c;
+
+ fprintf(stderr, "ESC[");
+ for (i = 0; i < csiescseq.len; i++) {
+ c = csiescseq.buf[i] & 0xff;
+ if (isprint(c)) {
+ putc(c, stderr);
+ } else if (c == '\n') {
+ fprintf(stderr, "(\\n)");
+ } else if (c == '\r') {
+ fprintf(stderr, "(\\r)");
+ } else if (c == 0x1b) {
+ fprintf(stderr, "(\\e)");
+ } else {
+ fprintf(stderr, "(%02x)", c);
+ }
+ }
+ putc('\n', stderr);
+}
+
+void
+csireset(void)
+{
+ memset(&csiescseq, 0, sizeof(csiescseq));
+}
+
+void
+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/sto/st.c.orig b/sto/st.c.orig
new file mode 100755
index 0000000..62def59
--- /dev/null
+++ b/sto/st.c.orig
@@ -0,0 +1,2656 @@
+/* See LICENSE for license details. */
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "st.h"
+#include "win.h"
+
+#if defined(__linux)
+ #include <pty.h>
+#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+ #include <util.h>
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+ #include <libutil.h>
+#endif
+
+/* Arbitrary sizes */
+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+#define ESC_BUF_SIZ (128*UTF_SIZ)
+#define ESC_ARG_SIZ 16
+#define STR_BUF_SIZ ESC_BUF_SIZ
+#define STR_ARG_SIZ ESC_ARG_SIZ
+
+/* macros */
+#define IS_SET(flag) ((term.mode & (flag)) != 0)
+#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
+#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
+#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
+#define ISDELIM(u) (u && wcschr(worddelimiters, u))
+
+enum term_mode {
+ MODE_WRAP = 1 << 0,
+ MODE_INSERT = 1 << 1,
+ MODE_ALTSCREEN = 1 << 2,
+ MODE_CRLF = 1 << 3,
+ MODE_ECHO = 1 << 4,
+ MODE_PRINT = 1 << 5,
+ MODE_UTF8 = 1 << 6,
+};
+
+enum cursor_movement {
+ CURSOR_SAVE,
+ CURSOR_LOAD
+};
+
+enum cursor_state {
+ CURSOR_DEFAULT = 0,
+ CURSOR_WRAPNEXT = 1,
+ CURSOR_ORIGIN = 2
+};
+
+enum charset {
+ CS_GRAPHIC0,
+ CS_GRAPHIC1,
+ CS_UK,
+ CS_USA,
+ CS_MULTI,
+ CS_GER,
+ CS_FIN
+};
+
+enum escape_state {
+ ESC_START = 1,
+ ESC_CSI = 2,
+ ESC_STR = 4, /* DCS, OSC, PM, APC */
+ ESC_ALTCHARSET = 8,
+ ESC_STR_END = 16, /* a final string was encountered */
+ ESC_TEST = 32, /* Enter in test mode */
+ ESC_UTF8 = 64,
+};
+
+typedef struct {
+ Glyph attr; /* current char attributes */
+ int x;
+ int y;
+ char state;
+} TCursor;
+
+typedef struct {
+ int mode;
+ int type;
+ int snap;
+ /*
+ * Selection variables:
+ * nb – normalized coordinates of the beginning of the selection
+ * ne – normalized coordinates of the end of the selection
+ * ob – original coordinates of the beginning of the selection
+ * oe – original coordinates of the end of the selection
+ */
+ struct {
+ int x, y;
+ } nb, ne, ob, oe;
+
+ int alt;
+} Selection;
+
+/* Internal representation of the screen */
+typedef struct {
+ int row; /* nb row */
+ int col; /* nb col */
+ Line *line; /* screen */
+ Line *alt; /* alternate screen */
+ int *dirty; /* dirtyness of lines */
+ TCursor c; /* cursor */
+ int ocx; /* old cursor col */
+ int ocy; /* old cursor row */
+ int top; /* top scroll limit */
+ int bot; /* bottom scroll limit */
+ int mode; /* terminal mode flags */
+ int esc; /* escape state flags */
+ char trantbl[4]; /* charset table translation */
+ int charset; /* current charset */
+ int icharset; /* selected charset for sequence */
+ int *tabs;
+ Rune lastc; /* last printed char outside of sequence, 0 if control */
+} Term;
+
+/* CSI Escape sequence structs */
+/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
+typedef struct {
+ char buf[ESC_BUF_SIZ]; /* raw string */
+ size_t len; /* raw string length */
+ char priv;
+ int arg[ESC_ARG_SIZ];
+ int narg; /* nb of args */
+ char mode[2];
+} CSIEscape;
+
+/* STR Escape sequence structs */
+/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
+typedef struct {
+ char type; /* ESC type ... */
+ char *buf; /* allocated raw string */
+ size_t siz; /* allocation size */
+ size_t len; /* raw string length */
+ char *args[STR_ARG_SIZ];
+ int narg; /* nb of args */
+} STREscape;
+
+static void execsh(char *, char **);
+static void stty(char **);
+static void sigchld(int);
+static void ttywriteraw(const char *, size_t);
+
+static void csidump(void);
+static void csihandle(void);
+static void csiparse(void);
+static void csireset(void);
+static 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 <n> blank char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tinsertblank(csiescseq.arg[0]);
+ break;
+ case 'A': /* CUU -- Cursor <n> Up */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
+ break;
+ case 'B': /* CUD -- Cursor <n> Down */
+ case 'e': /* VPR --Cursor <n> Down */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
+ break;
+ case 'i': /* MC -- Media Copy */
+ switch (csiescseq.arg[0]) {
+ case 0:
+ tdump();
+ break;
+ case 1:
+ tdumpline(term.c.y);
+ break;
+ case 2:
+ tdumpsel();
+ break;
+ case 4:
+ term.mode &= ~MODE_PRINT;
+ break;
+ case 5:
+ term.mode |= MODE_PRINT;
+ break;
+ }
+ break;
+ case 'c': /* DA -- Device Attributes */
+ if (csiescseq.arg[0] == 0)
+ ttywrite(vtiden, strlen(vtiden), 0);
+ break;
+ case 'b': /* REP -- if last char is printable print it <n> more times */
+ DEFAULT(csiescseq.arg[0], 1);
+ if (term.lastc)
+ while (csiescseq.arg[0]-- > 0)
+ tputc(term.lastc);
+ break;
+ case 'C': /* CUF -- Cursor <n> Forward */
+ case 'a': /* HPR -- Cursor <n> Forward */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
+ break;
+ case 'D': /* CUB -- Cursor <n> Backward */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
+ break;
+ case 'E': /* CNL -- Cursor <n> Down and first col */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(0, term.c.y+csiescseq.arg[0]);
+ break;
+ case 'F': /* CPL -- Cursor <n> Up and first col */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(0, term.c.y-csiescseq.arg[0]);
+ break;
+ case 'g': /* TBC -- Tabulation clear */
+ switch (csiescseq.arg[0]) {
+ case 0: /* clear current tab stop */
+ term.tabs[term.c.x] = 0;
+ break;
+ case 3: /* clear all the tabs */
+ memset(term.tabs, 0, term.col * sizeof(*term.tabs));
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 'G': /* CHA -- Move to <col> */
+ case '`': /* HPA */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveto(csiescseq.arg[0]-1, term.c.y);
+ break;
+ case 'H': /* CUP -- Move to <row> <col> */
+ case 'f': /* HVP */
+ DEFAULT(csiescseq.arg[0], 1);
+ DEFAULT(csiescseq.arg[1], 1);
+ tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
+ break;
+ case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
+ DEFAULT(csiescseq.arg[0], 1);
+ tputtab(csiescseq.arg[0]);
+ break;
+ case 'J': /* ED -- Clear screen */
+ switch (csiescseq.arg[0]) {
+ case 0: /* below */
+ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
+ if (term.c.y < term.row-1) {
+ tclearregion(0, term.c.y+1, term.col-1,
+ term.row-1);
+ }
+ break;
+ case 1: /* above */
+ if (term.c.y > 1)
+ tclearregion(0, 0, term.col-1, term.c.y-1);
+ tclearregion(0, term.c.y, term.c.x, term.c.y);
+ break;
+ case 2: /* all */
+ tclearregion(0, 0, term.col-1, term.row-1);
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 'K': /* EL -- Clear line */
+ switch (csiescseq.arg[0]) {
+ case 0: /* right */
+ tclearregion(term.c.x, term.c.y, term.col-1,
+ term.c.y);
+ break;
+ case 1: /* left */
+ tclearregion(0, term.c.y, term.c.x, term.c.y);
+ break;
+ case 2: /* all */
+ tclearregion(0, term.c.y, term.col-1, term.c.y);
+ break;
+ }
+ break;
+ case 'S': /* SU -- Scroll <n> line up */
+ DEFAULT(csiescseq.arg[0], 1);
+ tscrollup(term.top, csiescseq.arg[0]);
+ break;
+ case 'T': /* SD -- Scroll <n> line down */
+ DEFAULT(csiescseq.arg[0], 1);
+ tscrolldown(term.top, csiescseq.arg[0]);
+ break;
+ case 'L': /* IL -- Insert <n> blank lines */
+ DEFAULT(csiescseq.arg[0], 1);
+ tinsertblankline(csiescseq.arg[0]);
+ break;
+ case 'l': /* RM -- Reset Mode */
+ tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
+ break;
+ case 'M': /* DL -- Delete <n> lines */
+ DEFAULT(csiescseq.arg[0], 1);
+ tdeleteline(csiescseq.arg[0]);
+ break;
+ case 'X': /* ECH -- Erase <n> char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tclearregion(term.c.x, term.c.y,
+ term.c.x + csiescseq.arg[0] - 1, term.c.y);
+ break;
+ case 'P': /* DCH -- Delete <n> char */
+ DEFAULT(csiescseq.arg[0], 1);
+ tdeletechar(csiescseq.arg[0]);
+ break;
+ case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
+ DEFAULT(csiescseq.arg[0], 1);
+ tputtab(-csiescseq.arg[0]);
+ break;
+ case 'd': /* VPA -- Move to <row> */
+ DEFAULT(csiescseq.arg[0], 1);
+ tmoveato(term.c.x, csiescseq.arg[0]-1);
+ break;
+ case 'h': /* SM -- Set terminal mode */
+ tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
+ break;
+ case 'm': /* SGR -- Terminal attribute (color) */
+ tsetattr(csiescseq.arg, csiescseq.narg);
+ break;
+ case 'n': /* DSR – Device Status Report (cursor position) */
+ if (csiescseq.arg[0] == 6) {
+ len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
+ term.c.y+1, term.c.x+1);
+ ttywrite(buf, len, 0);
+ }
+ break;
+ case 'r': /* DECSTBM -- Set Scrolling Region */
+ if (csiescseq.priv) {
+ goto unknown;
+ } else {
+ DEFAULT(csiescseq.arg[0], 1);
+ DEFAULT(csiescseq.arg[1], term.row);
+ tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
+ tmoveato(0, 0);
+ }
+ break;
+ case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
+ tcursor(CURSOR_SAVE);
+ break;
+ case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
+ tcursor(CURSOR_LOAD);
+ break;
+ case ' ':
+ switch (csiescseq.mode[1]) {
+ case 'q': /* DECSCUSR -- Set Cursor Style */
+ if (xsetcursor(csiescseq.arg[0]))
+ goto unknown;
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ }
+}
+
+void
+csidump(void)
+{
+ size_t i;
+ uint c;
+
+ fprintf(stderr, "ESC[");
+ for (i = 0; i < csiescseq.len; i++) {
+ c = csiescseq.buf[i] & 0xff;
+ if (isprint(c)) {
+ putc(c, stderr);
+ } else if (c == '\n') {
+ fprintf(stderr, "(\\n)");
+ } else if (c == '\r') {
+ fprintf(stderr, "(\\r)");
+ } else if (c == 0x1b) {
+ fprintf(stderr, "(\\e)");
+ } else {
+ fprintf(stderr, "(%02x)", c);
+ }
+ }
+ putc('\n', stderr);
+}
+
+void
+csireset(void)
+{
+ memset(&csiescseq, 0, sizeof(csiescseq));
+}
+
+void
+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/sto/st.h b/sto/st.h
new file mode 100755
index 0000000..818a6f8
--- /dev/null
+++ b/sto/st.h
@@ -0,0 +1,128 @@
+/* See LICENSE for license details. */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+/* macros */
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) < (b) ? (b) : (a))
+#define LEN(a) (sizeof(a) / sizeof(a)[0])
+#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
+#define DEFAULT(a, b) (a) = (a) ? (a) : (b)
+#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
+#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
+ (a).bg != (b).bg)
+#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
+ (t1.tv_nsec-t2.tv_nsec)/1E6)
+#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+
+#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
+#define IS_TRUECOL(x) (1 << 24 & (x))
+
+enum glyph_attribute {
+ ATTR_NULL = 0,
+ ATTR_BOLD = 1 << 0,
+ ATTR_FAINT = 1 << 1,
+ ATTR_ITALIC = 1 << 2,
+ ATTR_UNDERLINE = 1 << 3,
+ ATTR_BLINK = 1 << 4,
+ ATTR_REVERSE = 1 << 5,
+ ATTR_INVISIBLE = 1 << 6,
+ ATTR_STRUCK = 1 << 7,
+ ATTR_WRAP = 1 << 8,
+ ATTR_WIDE = 1 << 9,
+ ATTR_WDUMMY = 1 << 10,
+ ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
+};
+
+enum selection_mode {
+ SEL_IDLE = 0,
+ SEL_EMPTY = 1,
+ SEL_READY = 2
+};
+
+enum selection_type {
+ SEL_REGULAR = 1,
+ SEL_RECTANGULAR = 2
+};
+
+enum selection_snap {
+ SNAP_WORD = 1,
+ SNAP_LINE = 2
+};
+
+typedef unsigned char uchar;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+typedef unsigned short ushort;
+
+typedef uint_least32_t Rune;
+
+#define Glyph Glyph_
+typedef struct {
+ Rune u; /* character code */
+ ushort mode; /* attribute flags */
+ uint32_t fg; /* foreground */
+ uint32_t bg; /* background */
+} Glyph;
+
+typedef Glyph *Line;
+
+typedef union {
+ int i;
+ uint ui;
+ float f;
+ const void *v;
+ const char *s;
+} Arg;
+
+void die(const char *, ...);
+void redraw(void);
+void draw(void);
+
+void kscrolldown(const Arg *);
+void kscrollup(const Arg *);
+void printscreen(const Arg *);
+void printsel(const Arg *);
+void sendbreak(const Arg *);
+void toggleprinter(const Arg *);
+
+int tattrset(int);
+void tnew(int, int);
+void tresize(int, int);
+void tsetdirtattr(int);
+void ttyhangup(void);
+int ttynew(const char *, char *, const char *, char **);
+size_t ttyread(void);
+void ttyresize(int, int);
+void ttywrite(const char *, size_t, int);
+
+void resettitle(void);
+
+void selclear(void);
+void selinit(void);
+void selstart(int, int, int);
+void selextend(int, int, int, int);
+int selected(int, int);
+char *getsel(void);
+
+size_t utf8encode(Rune, char *);
+
+void *xmalloc(size_t);
+void *xrealloc(void *, size_t);
+char *xstrdup(const char *);
+
+/* config.h globals */
+extern char *utmp;
+extern char *scroll;
+extern char *stty_args;
+extern char *vtiden;
+extern wchar_t *worddelimiters;
+extern int allowaltscreen;
+extern int allowwindowops;
+extern char *termname;
+extern unsigned int tabspaces;
+extern unsigned int defaultfg;
+extern unsigned int defaultbg;
+extern unsigned int defaultcs;
diff --git a/sto/st.info b/sto/st.info
new file mode 100755
index 0000000..8201ad6
--- /dev/null
+++ b/sto/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/sto/st.o b/sto/st.o
new file mode 100644
index 0000000..f637737
--- /dev/null
+++ b/sto/st.o
Binary files differ
diff --git a/sto/win.h b/sto/win.h
new file mode 100755
index 0000000..6de960d
--- /dev/null
+++ b/sto/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/sto/x.c b/sto/x.c
new file mode 100755
index 0000000..aa09997
--- /dev/null
+++ b/sto/x.c
@@ -0,0 +1,2099 @@
+/* See LICENSE for license details. */
+#include <errno.h>
+#include <math.h>
+#include <limits.h>
+#include <locale.h>
+#include <signal.h>
+#include <sys/select.h>
+#include <time.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/cursorfont.h>
+#include <X11/keysym.h>
+#include <X11/Xft/Xft.h>
+#include <X11/XKBlib.h>
+
+char *argv0;
+#include "arg.h"
+#include "st.h"
+#include "win.h"
+
+/* types used in config.h */
+typedef struct {
+ uint mod;
+ KeySym keysym;
+ void (*func)(const Arg *);
+ const Arg arg;
+} Shortcut;
+
+typedef struct {
+ uint mod;
+ uint button;
+ void (*func)(const Arg *);
+ const Arg arg;
+ uint release;
+} MouseShortcut;
+
+typedef struct {
+ KeySym k;
+ uint mask;
+ char *s;
+ /* three-valued logic variables: 0 indifferent, 1 on, -1 off */
+ signed char appkey; /* application keypad */
+ signed char appcursor; /* application cursor */
+} Key;
+
+/* X modifiers */
+#define XK_ANY_MOD UINT_MAX
+#define XK_NO_MOD 0
+#define XK_SWITCH_MOD (1<<13|1<<14)
+
+/* function definitions used in config.h */
+static void clipcopy(const Arg *);
+static void clippaste(const Arg *);
+static void numlock(const Arg *);
+static void selpaste(const Arg *);
+static void zoom(const Arg *);
+static void zoomabs(const Arg *);
+static void zoomreset(const Arg *);
+static void ttysend(const Arg *);
+
+/* config.h for applying patches and the configuration. */
+#include "config.h"
+
+/* XEMBED messages */
+#define XEMBED_FOCUS_IN 4
+#define XEMBED_FOCUS_OUT 5
+
+/* macros */
+#define IS_SET(flag) ((win.mode & (flag)) != 0)
+#define TRUERED(x) (((x) & 0xff0000) >> 8)
+#define TRUEGREEN(x) (((x) & 0xff00))
+#define TRUEBLUE(x) (((x) & 0xff) << 8)
+
+typedef XftDraw *Draw;
+typedef XftColor Color;
+typedef XftGlyphFontSpec GlyphFontSpec;
+
+/* Purely graphic info */
+typedef struct {
+ int tw, th; /* tty width and height */
+ int w, h; /* window width and height */
+ int ch; /* char height */
+ int cw; /* char width */
+ int mode; /* window state/mode flags */
+ int cursor; /* cursor style */
+} TermWindow;
+
+typedef struct {
+ Display *dpy;
+ Colormap cmap;
+ Window win;
+ Drawable buf;
+ GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
+ Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
+ struct {
+ XIM xim;
+ XIC xic;
+ XPoint spot;
+ XVaNestedList spotlist;
+ } ime;
+ Draw draw;
+ Visual *vis;
+ XSetWindowAttributes attrs;
+ int scr;
+ int isfixed; /* is fixed geometry? */
+ int l, t; /* left and top offset */
+ int gm; /* geometry mask */
+} XWindow;
+
+typedef struct {
+ Atom xtarget;
+ char *primary, *clipboard;
+ struct timespec tclick1;
+ struct timespec tclick2;
+} XSelection;
+
+/* Font structure */
+#define Font Font_
+typedef struct {
+ int height;
+ int width;
+ int ascent;
+ int descent;
+ int badslant;
+ int badweight;
+ short lbearing;
+ short rbearing;
+ XftFont *match;
+ FcFontSet *set;
+ FcPattern *pattern;
+} Font;
+
+/* Drawing Context */
+typedef struct {
+ Color *col;
+ size_t collen;
+ Font font, bfont, ifont, ibfont;
+ GC gc;
+} DC;
+
+static inline ushort sixd_to_16bit(int);
+static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
+static void xdrawglyph(Glyph, int, int);
+static void xclear(int, int, int, int);
+static int xgeommasktogravity(int);
+static int ximopen(Display *);
+static void ximinstantiate(Display *, XPointer, XPointer);
+static void ximdestroy(XIM, XPointer, XPointer);
+static int xicdestroy(XIC, XPointer, XPointer);
+static void xinit(int, int);
+static void cresize(int, int);
+static void xresize(int, int);
+static void xhints(void);
+static int xloadcolor(int, const char *, Color *);
+static int xloadfont(Font *, FcPattern *);
+static void xloadfonts(const char *, double);
+static void xunloadfont(Font *);
+static void xunloadfonts(void);
+static void xsetenv(void);
+static void xseturgency(int);
+static int evcol(XEvent *);
+static int evrow(XEvent *);
+
+static void expose(XEvent *);
+static void visibility(XEvent *);
+static void unmap(XEvent *);
+static void kpress(XEvent *);
+static void cmessage(XEvent *);
+static void resize(XEvent *);
+static void focus(XEvent *);
+static uint buttonmask(uint);
+static int mouseaction(XEvent *, uint);
+static void brelease(XEvent *);
+static void bpress(XEvent *);
+static void bmotion(XEvent *);
+static void propnotify(XEvent *);
+static void selnotify(XEvent *);
+static void selclear_(XEvent *);
+static void selrequest(XEvent *);
+static void setsel(char *, Time);
+static void mousesel(XEvent *, int);
+static void mousereport(XEvent *);
+static char *kmap(KeySym, uint);
+static int match(uint, uint);
+
+static void run(void);
+static void usage(void);
+
+static void (*handler[LASTEvent])(XEvent *) = {
+ [KeyPress] = kpress,
+ [ClientMessage] = cmessage,
+ [ConfigureNotify] = resize,
+ [VisibilityNotify] = visibility,
+ [UnmapNotify] = unmap,
+ [Expose] = expose,
+ [FocusIn] = focus,
+ [FocusOut] = focus,
+ [MotionNotify] = bmotion,
+ [ButtonPress] = bpress,
+ [ButtonRelease] = brelease,
+/*
+ * Uncomment if you want the selection to disappear when you select something
+ * different in another window.
+ */
+/* [SelectionClear] = selclear_, */
+ [SelectionNotify] = selnotify,
+/*
+ * PropertyNotify is only turned on when there is some INCR transfer happening
+ * for the selection retrieval.
+ */
+ [PropertyNotify] = propnotify,
+ [SelectionRequest] = selrequest,
+};
+
+/* Globals */
+static DC dc;
+static XWindow xw;
+static XSelection xsel;
+static TermWindow win;
+
+/* Font Ring Cache */
+enum {
+ FRC_NORMAL,
+ FRC_ITALIC,
+ FRC_BOLD,
+ FRC_ITALICBOLD
+};
+
+typedef struct {
+ XftFont *font;
+ int flags;
+ Rune unicodep;
+} Fontcache;
+
+/* Fontcache is an array now. A new font will be appended to the array. */
+static Fontcache *frc = NULL;
+static int frclen = 0;
+static int frccap = 0;
+static char *usedfont = NULL;
+static double usedfontsize = 0;
+static double defaultfontsize = 0;
+
+static char *opt_class = NULL;
+static char **opt_cmd = NULL;
+static char *opt_embed = NULL;
+static char *opt_font = NULL;
+static char *opt_io = NULL;
+static char *opt_line = NULL;
+static char *opt_name = NULL;
+static char *opt_title = NULL;
+
+static uint buttons; /* bit field of pressed buttons */
+
+void
+clipcopy(const Arg *dummy)
+{
+ Atom clipboard;
+
+ free(xsel.clipboard);
+ xsel.clipboard = NULL;
+
+ if (xsel.primary != NULL) {
+ xsel.clipboard = xstrdup(xsel.primary);
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
+ }
+}
+
+void
+clippaste(const Arg *dummy)
+{
+ Atom clipboard;
+
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard,
+ xw.win, CurrentTime);
+}
+
+void
+selpaste(const Arg *dummy)
+{
+ XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY,
+ xw.win, CurrentTime);
+}
+
+void
+numlock(const Arg *dummy)
+{
+ win.mode ^= MODE_NUMLOCK;
+}
+
+void
+zoom(const Arg *arg)
+{
+ Arg larg;
+
+ larg.f = usedfontsize + arg->f;
+ zoomabs(&larg);
+}
+
+void
+zoomabs(const Arg *arg)
+{
+ xunloadfonts();
+ xloadfonts(usedfont, arg->f);
+ cresize(0, 0);
+ redraw();
+ xhints();
+}
+
+void
+zoomreset(const Arg *arg)
+{
+ Arg larg;
+
+ if (defaultfontsize > 0) {
+ larg.f = defaultfontsize;
+ zoomabs(&larg);
+ }
+}
+
+void
+ttysend(const Arg *arg)
+{
+ ttywrite(arg->s, strlen(arg->s), 1);
+}
+
+int
+evcol(XEvent *e)
+{
+ int x = e->xbutton.x - borderpx;
+ LIMIT(x, 0, win.tw - 1);
+ return x / win.cw;
+}
+
+int
+evrow(XEvent *e)
+{
+ int y = e->xbutton.y - borderpx;
+ LIMIT(y, 0, win.th - 1);
+ return y / win.ch;
+}
+
+void
+mousesel(XEvent *e, int done)
+{
+ int type, seltype = SEL_REGULAR;
+ uint state = e->xbutton.state & ~(Button1Mask | forcemousemod);
+
+ for (type = 1; type < LEN(selmasks); ++type) {
+ if (match(selmasks[type], state)) {
+ seltype = type;
+ break;
+ }
+ }
+ selextend(evcol(e), evrow(e), seltype, done);
+ if (done)
+ setsel(getsel(), e->xbutton.time);
+}
+
+void
+mousereport(XEvent *e)
+{
+ int len, btn, code;
+ int x = evcol(e), y = evrow(e);
+ int state = e->xbutton.state;
+ char buf[40];
+ static int ox, oy;
+
+ if (e->type == MotionNotify) {
+ if (x == ox && y == oy)
+ return;
+ if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
+ return;
+ /* MODE_MOUSEMOTION: no reporting if no button is pressed */
+ if (IS_SET(MODE_MOUSEMOTION) && buttons == 0)
+ return;
+ /* Set btn to lowest-numbered pressed button, or 12 if no
+ * buttons are pressed. */
+ for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++)
+ ;
+ code = 32;
+ } else {
+ btn = e->xbutton.button;
+ /* Only buttons 1 through 11 can be encoded */
+ if (btn < 1 || btn > 11)
+ return;
+ if (e->type == ButtonRelease) {
+ /* MODE_MOUSEX10: no button release reporting */
+ if (IS_SET(MODE_MOUSEX10))
+ return;
+ /* Don't send release events for the scroll wheel */
+ if (btn == 4 || btn == 5)
+ return;
+ }
+ code = 0;
+ }
+
+ ox = x;
+ oy = y;
+
+ /* Encode btn into code. If no button is pressed for a motion event in
+ * MODE_MOUSEMANY, then encode it as a release. */
+ if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12)
+ code += 3;
+ else if (btn >= 8)
+ code += 128 + btn - 8;
+ else if (btn >= 4)
+ code += 64 + btn - 4;
+ else
+ code += btn - 1;
+
+ if (!IS_SET(MODE_MOUSEX10)) {
+ code += ((state & ShiftMask ) ? 4 : 0)
+ + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */
+ + ((state & ControlMask) ? 16 : 0);
+ }
+
+ if (IS_SET(MODE_MOUSESGR)) {
+ len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
+ code, x+1, y+1,
+ e->type == ButtonRelease ? 'm' : 'M');
+ } else if (x < 223 && y < 223) {
+ len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
+ 32+code, 32+x+1, 32+y+1);
+ } else {
+ return;
+ }
+
+ ttywrite(buf, len, 0);
+}
+
+uint
+buttonmask(uint button)
+{
+ return button == Button1 ? Button1Mask
+ : button == Button2 ? Button2Mask
+ : button == Button3 ? Button3Mask
+ : button == Button4 ? Button4Mask
+ : button == Button5 ? Button5Mask
+ : 0;
+}
+
+int
+mouseaction(XEvent *e, uint release)
+{
+ MouseShortcut *ms;
+
+ /* ignore Button<N>mask for Button<N> - it's set on release */
+ uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
+
+ for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
+ if (ms->release == release &&
+ ms->button == e->xbutton.button &&
+ (match(ms->mod, state) || /* exact or forced */
+ match(ms->mod, state & ~forcemousemod))) {
+ ms->func(&(ms->arg));
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+bpress(XEvent *e)
+{
+ int btn = e->xbutton.button;
+ struct timespec now;
+ int snap;
+
+ if (1 <= btn && btn <= 11)
+ buttons |= 1 << (btn-1);
+
+ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
+ mousereport(e);
+ return;
+ }
+
+ if (mouseaction(e, 0))
+ return;
+
+ if (btn == Button1) {
+ /*
+ * If the user clicks below predefined timeouts specific
+ * snapping behaviour is exposed.
+ */
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) {
+ snap = SNAP_LINE;
+ } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) {
+ snap = SNAP_WORD;
+ } else {
+ snap = 0;
+ }
+ xsel.tclick2 = xsel.tclick1;
+ xsel.tclick1 = now;
+
+ selstart(evcol(e), evrow(e), snap);
+ }
+}
+
+void
+propnotify(XEvent *e)
+{
+ XPropertyEvent *xpev;
+ Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+
+ xpev = &e->xproperty;
+ if (xpev->state == PropertyNewValue &&
+ (xpev->atom == XA_PRIMARY ||
+ xpev->atom == clipboard)) {
+ selnotify(e);
+ }
+}
+
+void
+selnotify(XEvent *e)
+{
+ ulong nitems, ofs, rem;
+ int format;
+ uchar *data, *last, *repl;
+ Atom type, incratom, property = None;
+
+ incratom = XInternAtom(xw.dpy, "INCR", 0);
+
+ ofs = 0;
+ if (e->type == SelectionNotify)
+ property = e->xselection.property;
+ else if (e->type == PropertyNotify)
+ property = e->xproperty.atom;
+
+ if (property == None)
+ return;
+
+ do {
+ if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
+ BUFSIZ/4, False, AnyPropertyType,
+ &type, &format, &nitems, &rem,
+ &data)) {
+ fprintf(stderr, "Clipboard allocation failed\n");
+ return;
+ }
+
+ if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
+ /*
+ * If there is some PropertyNotify with no data, then
+ * this is the signal of the selection owner that all
+ * data has been transferred. We won't need to receive
+ * PropertyNotify events anymore.
+ */
+ MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
+ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
+ &xw.attrs);
+ }
+
+ if (type == incratom) {
+ /*
+ * Activate the PropertyNotify events so we receive
+ * when the selection owner does send us the next
+ * chunk of data.
+ */
+ MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
+ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
+ &xw.attrs);
+
+ /*
+ * Deleting the property is the transfer start signal.
+ */
+ XDeleteProperty(xw.dpy, xw.win, (int)property);
+ continue;
+ }
+
+ /*
+ * As seen in getsel:
+ * Line endings are inconsistent in the terminal and GUI world
+ * copy and pasting. When receiving some selection data,
+ * replace all '\n' with '\r'.
+ * FIXME: Fix the computer world.
+ */
+ repl = data;
+ last = data + nitems * format / 8;
+ while ((repl = memchr(repl, '\n', last - repl))) {
+ *repl++ = '\r';
+ }
+
+ if (IS_SET(MODE_BRCKTPASTE) && ofs == 0)
+ ttywrite("\033[200~", 6, 0);
+ ttywrite((char *)data, nitems * format / 8, 1);
+ if (IS_SET(MODE_BRCKTPASTE) && rem == 0)
+ ttywrite("\033[201~", 6, 0);
+ XFree(data);
+ /* number of 32-bit chunks returned */
+ ofs += nitems * format / 32;
+ } while (rem > 0);
+
+ /*
+ * Deleting the property again tells the selection owner to send the
+ * next data chunk in the property.
+ */
+ XDeleteProperty(xw.dpy, xw.win, (int)property);
+}
+
+void
+xclipcopy(void)
+{
+ clipcopy(NULL);
+}
+
+void
+selclear_(XEvent *e)
+{
+ selclear();
+}
+
+void
+selrequest(XEvent *e)
+{
+ XSelectionRequestEvent *xsre;
+ XSelectionEvent xev;
+ Atom xa_targets, string, clipboard;
+ char *seltext;
+
+ xsre = (XSelectionRequestEvent *) e;
+ xev.type = SelectionNotify;
+ xev.requestor = xsre->requestor;
+ xev.selection = xsre->selection;
+ xev.target = xsre->target;
+ xev.time = xsre->time;
+ if (xsre->property == None)
+ xsre->property = xsre->target;
+
+ /* reject */
+ xev.property = None;
+
+ xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
+ if (xsre->target == xa_targets) {
+ /* respond with the supported type */
+ string = xsel.xtarget;
+ XChangeProperty(xsre->display, xsre->requestor, xsre->property,
+ XA_ATOM, 32, PropModeReplace,
+ (uchar *) &string, 1);
+ xev.property = xsre->property;
+ } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) {
+ /*
+ * xith XA_STRING non ascii characters may be incorrect in the
+ * requestor. It is not our problem, use utf8.
+ */
+ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
+ if (xsre->selection == XA_PRIMARY) {
+ seltext = xsel.primary;
+ } else if (xsre->selection == clipboard) {
+ seltext = xsel.clipboard;
+ } else {
+ fprintf(stderr,
+ "Unhandled clipboard selection 0x%lx\n",
+ xsre->selection);
+ return;
+ }
+ if (seltext != NULL) {
+ XChangeProperty(xsre->display, xsre->requestor,
+ xsre->property, xsre->target,
+ 8, PropModeReplace,
+ (uchar *)seltext, strlen(seltext));
+ xev.property = xsre->property;
+ }
+ }
+
+ /* all done, send a notification to the listener */
+ if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))
+ fprintf(stderr, "Error sending SelectionNotify event\n");
+}
+
+void
+setsel(char *str, Time t)
+{
+ if (!str)
+ return;
+
+ free(xsel.primary);
+ xsel.primary = str;
+
+ XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
+ if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
+ selclear();
+}
+
+void
+xsetsel(char *str)
+{
+ setsel(str, CurrentTime);
+}
+
+void
+brelease(XEvent *e)
+{
+ int btn = e->xbutton.button;
+
+ if (1 <= btn && btn <= 11)
+ buttons &= ~(1 << (btn-1));
+
+ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
+ mousereport(e);
+ return;
+ }
+
+ if (mouseaction(e, 1))
+ return;
+ if (btn == Button1)
+ mousesel(e, 1);
+}
+
+void
+bmotion(XEvent *e)
+{
+ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
+ mousereport(e);
+ return;
+ }
+
+ mousesel(e, 0);
+}
+
+void
+cresize(int width, int height)
+{
+ int col, row;
+
+ if (width != 0)
+ win.w = width;
+ if (height != 0)
+ win.h = height;
+
+ col = (win.w - 2 * borderpx) / win.cw;
+ row = (win.h - 2 * borderpx) / win.ch;
+ col = MAX(1, col);
+ row = MAX(1, row);
+
+ tresize(col, row);
+ xresize(col, row);
+ ttyresize(win.tw, win.th);
+}
+
+void
+xresize(int col, int row)
+{
+ win.tw = col * win.cw;
+ win.th = row * win.ch;
+
+ XFreePixmap(xw.dpy, xw.buf);
+ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
+ DefaultDepth(xw.dpy, xw.scr));
+ XftDrawChange(xw.draw, xw.buf);
+ xclear(0, 0, win.w, win.h);
+
+ /* resize to new width */
+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
+}
+
+ushort
+sixd_to_16bit(int x)
+{
+ return x == 0 ? 0 : 0x3737 + 0x2828 * x;
+}
+
+int
+xloadcolor(int i, const char *name, Color *ncolor)
+{
+ XRenderColor color = { .alpha = 0xffff };
+
+ if (!name) {
+ if (BETWEEN(i, 16, 255)) { /* 256 color */
+ if (i < 6*6*6+16) { /* same colors as xterm */
+ color.red = sixd_to_16bit( ((i-16)/36)%6 );
+ color.green = sixd_to_16bit( ((i-16)/6) %6 );
+ color.blue = sixd_to_16bit( ((i-16)/1) %6 );
+ } else { /* greyscale */
+ color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));
+ color.green = color.blue = color.red;
+ }
+ return XftColorAllocValue(xw.dpy, xw.vis,
+ xw.cmap, &color, ncolor);
+ } else
+ name = colorname[i];
+ }
+
+ return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
+}
+
+void
+xloadcols(void)
+{
+ int i;
+ static int loaded;
+ Color *cp;
+
+ if (loaded) {
+ for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
+ XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
+ } else {
+ dc.collen = MAX(LEN(colorname), 256);
+ dc.col = xmalloc(dc.collen * sizeof(Color));
+ }
+
+ for (i = 0; i < dc.collen; i++)
+ if (!xloadcolor(i, NULL, &dc.col[i])) {
+ if (colorname[i])
+ die("could not allocate color '%s'\n", colorname[i]);
+ else
+ die("could not allocate color %d\n", i);
+ }
+ loaded = 1;
+}
+
+int
+xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b)
+{
+ if (!BETWEEN(x, 0, dc.collen))
+ return 1;
+
+ *r = dc.col[x].color.red >> 8;
+ *g = dc.col[x].color.green >> 8;
+ *b = dc.col[x].color.blue >> 8;
+
+ return 0;
+}
+
+int
+xsetcolorname(int x, const char *name)
+{
+ Color ncolor;
+
+ if (!BETWEEN(x, 0, dc.collen))
+ return 1;
+
+ if (!xloadcolor(x, name, &ncolor))
+ return 1;
+
+ XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
+ dc.col[x] = ncolor;
+
+ return 0;
+}
+
+/*
+ * Absolute coordinates.
+ */
+void
+xclear(int x1, int y1, int x2, int y2)
+{
+ XftDrawRect(xw.draw,
+ &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
+ x1, y1, x2-x1, y2-y1);
+}
+
+void
+xhints(void)
+{
+ XClassHint class = {opt_name ? opt_name : termname,
+ opt_class ? opt_class : termname};
+ XWMHints wm = {.flags = InputHint, .input = 1};
+ XSizeHints *sizeh;
+
+ sizeh = XAllocSizeHints();
+
+ sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
+ sizeh->height = win.h;
+ sizeh->width = win.w;
+ sizeh->height_inc = win.ch;
+ sizeh->width_inc = win.cw;
+ sizeh->base_height = 2 * borderpx;
+ sizeh->base_width = 2 * borderpx;
+ sizeh->min_height = win.ch + 2 * borderpx;
+ sizeh->min_width = win.cw + 2 * borderpx;
+ if (xw.isfixed) {
+ sizeh->flags |= PMaxSize;
+ sizeh->min_width = sizeh->max_width = win.w;
+ sizeh->min_height = sizeh->max_height = win.h;
+ }
+ if (xw.gm & (XValue|YValue)) {
+ sizeh->flags |= USPosition | PWinGravity;
+ sizeh->x = xw.l;
+ sizeh->y = xw.t;
+ sizeh->win_gravity = xgeommasktogravity(xw.gm);
+ }
+
+ XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm,
+ &class);
+ XFree(sizeh);
+}
+
+int
+xgeommasktogravity(int mask)
+{
+ switch (mask & (XNegative|YNegative)) {
+ case 0:
+ return NorthWestGravity;
+ case XNegative:
+ return NorthEastGravity;
+ case YNegative:
+ return SouthWestGravity;
+ }
+
+ return SouthEastGravity;
+}
+
+int
+xloadfont(Font *f, FcPattern *pattern)
+{
+ FcPattern *configured;
+ FcPattern *match;
+ FcResult result;
+ XGlyphInfo extents;
+ int wantattr, haveattr;
+
+ /*
+ * Manually configure instead of calling XftMatchFont
+ * so that we can use the configured pattern for
+ * "missing glyph" lookups.
+ */
+ configured = FcPatternDuplicate(pattern);
+ if (!configured)
+ return 1;
+
+ FcConfigSubstitute(NULL, configured, FcMatchPattern);
+ XftDefaultSubstitute(xw.dpy, xw.scr, configured);
+
+ match = FcFontMatch(NULL, configured, &result);
+ if (!match) {
+ FcPatternDestroy(configured);
+ return 1;
+ }
+
+ if (!(f->match = XftFontOpenPattern(xw.dpy, match))) {
+ FcPatternDestroy(configured);
+ FcPatternDestroy(match);
+ return 1;
+ }
+
+ if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) ==
+ XftResultMatch)) {
+ /*
+ * Check if xft was unable to find a font with the appropriate
+ * slant but gave us one anyway. Try to mitigate.
+ */
+ if ((XftPatternGetInteger(f->match->pattern, "slant", 0,
+ &haveattr) != XftResultMatch) || haveattr < wantattr) {
+ f->badslant = 1;
+ fputs("font slant does not match\n", stderr);
+ }
+ }
+
+ if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) ==
+ XftResultMatch)) {
+ if ((XftPatternGetInteger(f->match->pattern, "weight", 0,
+ &haveattr) != XftResultMatch) || haveattr != wantattr) {
+ f->badweight = 1;
+ fputs("font weight does not match\n", stderr);
+ }
+ }
+
+ XftTextExtentsUtf8(xw.dpy, f->match,
+ (const FcChar8 *) ascii_printable,
+ strlen(ascii_printable), &extents);
+
+ f->set = NULL;
+ f->pattern = configured;
+
+ f->ascent = f->match->ascent;
+ f->descent = f->match->descent;
+ f->lbearing = 0;
+ f->rbearing = f->match->max_advance_width;
+
+ f->height = f->ascent + f->descent;
+ f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));
+
+ return 0;
+}
+
+void
+xloadfonts(const char *fontstr, double fontsize)
+{
+ FcPattern *pattern;
+ double fontval;
+
+ if (fontstr[0] == '-')
+ pattern = XftXlfdParse(fontstr, False, False);
+ else
+ pattern = FcNameParse((const FcChar8 *)fontstr);
+
+ if (!pattern)
+ die("can't open font %s\n", fontstr);
+
+ if (fontsize > 1) {
+ FcPatternDel(pattern, FC_PIXEL_SIZE);
+ FcPatternDel(pattern, FC_SIZE);
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);
+ usedfontsize = fontsize;
+ } else {
+ if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
+ FcResultMatch) {
+ usedfontsize = fontval;
+ } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
+ FcResultMatch) {
+ usedfontsize = -1;
+ } else {
+ /*
+ * Default font size is 12, if none given. This is to
+ * have a known usedfontsize value.
+ */
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
+ usedfontsize = 12;
+ }
+ defaultfontsize = usedfontsize;
+ }
+
+ if (xloadfont(&dc.font, pattern))
+ die("can't open font %s\n", fontstr);
+
+ if (usedfontsize < 0) {
+ FcPatternGetDouble(dc.font.match->pattern,
+ FC_PIXEL_SIZE, 0, &fontval);
+ usedfontsize = fontval;
+ if (fontsize == 0)
+ defaultfontsize = fontval;
+ }
+
+ /* Setting character width and height. */
+ win.cw = ceilf(dc.font.width * cwscale);
+ win.ch = ceilf(dc.font.height * chscale);
+
+ FcPatternDel(pattern, FC_SLANT);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
+ if (xloadfont(&dc.ifont, pattern))
+ die("can't open font %s\n", fontstr);
+
+ FcPatternDel(pattern, FC_WEIGHT);
+ FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
+ if (xloadfont(&dc.ibfont, pattern))
+ die("can't open font %s\n", fontstr);
+
+ FcPatternDel(pattern, FC_SLANT);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
+ if (xloadfont(&dc.bfont, pattern))
+ die("can't open font %s\n", fontstr);
+
+ FcPatternDestroy(pattern);
+}
+
+void
+xunloadfont(Font *f)
+{
+ XftFontClose(xw.dpy, f->match);
+ FcPatternDestroy(f->pattern);
+ if (f->set)
+ FcFontSetDestroy(f->set);
+}
+
+void
+xunloadfonts(void)
+{
+ /* Free the loaded fonts in the font cache. */
+ while (frclen > 0)
+ XftFontClose(xw.dpy, frc[--frclen].font);
+
+ xunloadfont(&dc.font);
+ xunloadfont(&dc.bfont);
+ xunloadfont(&dc.ifont);
+ xunloadfont(&dc.ibfont);
+}
+
+int
+ximopen(Display *dpy)
+{
+ XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
+ XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
+
+ xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
+ if (xw.ime.xim == NULL)
+ return 0;
+
+ if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL))
+ fprintf(stderr, "XSetIMValues: "
+ "Could not set XNDestroyCallback.\n");
+
+ xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot,
+ NULL);
+
+ if (xw.ime.xic == NULL) {
+ xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,
+ XIMPreeditNothing | XIMStatusNothing,
+ XNClientWindow, xw.win,
+ XNDestroyCallback, &icdestroy,
+ NULL);
+ }
+ if (xw.ime.xic == NULL)
+ fprintf(stderr, "XCreateIC: Could not create input context.\n");
+
+ return 1;
+}
+
+void
+ximinstantiate(Display *dpy, XPointer client, XPointer call)
+{
+ if (ximopen(dpy))
+ XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+ ximinstantiate, NULL);
+}
+
+void
+ximdestroy(XIM xim, XPointer client, XPointer call)
+{
+ xw.ime.xim = NULL;
+ XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+ ximinstantiate, NULL);
+ XFree(xw.ime.spotlist);
+}
+
+int
+xicdestroy(XIC xim, XPointer client, XPointer call)
+{
+ xw.ime.xic = NULL;
+ return 1;
+}
+
+void
+xinit(int cols, int rows)
+{
+ XGCValues gcvalues;
+ Cursor cursor;
+ Window parent;
+ pid_t thispid = getpid();
+ XColor xmousefg, xmousebg;
+
+ if (!(xw.dpy = XOpenDisplay(NULL)))
+ die("can't open display\n");
+ xw.scr = XDefaultScreen(xw.dpy);
+ xw.vis = XDefaultVisual(xw.dpy, xw.scr);
+
+ /* font */
+ if (!FcInit())
+ die("could not init fontconfig.\n");
+
+ usedfont = (opt_font == NULL)? font : opt_font;
+ xloadfonts(usedfont, 0);
+
+ /* colors */
+ xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
+ xloadcols();
+
+ /* adjust fixed window geometry */
+ win.w = 2 * borderpx + cols * win.cw;
+ win.h = 2 * borderpx + rows * win.ch;
+ if (xw.gm & XNegative)
+ xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
+ if (xw.gm & YNegative)
+ xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2;
+
+ /* Events */
+ xw.attrs.background_pixel = dc.col[defaultbg].pixel;
+ xw.attrs.border_pixel = dc.col[defaultbg].pixel;
+ xw.attrs.bit_gravity = NorthWestGravity;
+ xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask
+ | ExposureMask | VisibilityChangeMask | StructureNotifyMask
+ | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
+ xw.attrs.colormap = xw.cmap;
+
+ if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
+ parent = XRootWindow(xw.dpy, xw.scr);
+ xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
+ win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
+ xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
+ | CWEventMask | CWColormap, &xw.attrs);
+
+ memset(&gcvalues, 0, sizeof(gcvalues));
+ gcvalues.graphics_exposures = False;
+ dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
+ &gcvalues);
+ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
+ DefaultDepth(xw.dpy, xw.scr));
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
+
+ /* font spec buffer */
+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
+
+ /* Xft rendering context */
+ xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
+
+ /* input methods */
+ if (!ximopen(xw.dpy)) {
+ XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+ ximinstantiate, NULL);
+ }
+
+ /* white cursor, black outline */
+ cursor = XCreateFontCursor(xw.dpy, mouseshape);
+ XDefineCursor(xw.dpy, xw.win, cursor);
+
+ if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) {
+ xmousefg.red = 0xffff;
+ xmousefg.green = 0xffff;
+ xmousefg.blue = 0xffff;
+ }
+
+ if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) {
+ xmousebg.red = 0x0000;
+ xmousebg.green = 0x0000;
+ xmousebg.blue = 0x0000;
+ }
+
+ XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);
+
+ xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
+ xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
+ xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
+ xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);
+ XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
+
+ xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
+ XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
+ PropModeReplace, (uchar *)&thispid, 1);
+
+ win.mode = MODE_NUMLOCK;
+ resettitle();
+ xhints();
+ XMapWindow(xw.dpy, xw.win);
+ XSync(xw.dpy, False);
+
+ clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);
+ clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2);
+ xsel.primary = NULL;
+ xsel.clipboard = NULL;
+ xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
+ if (xsel.xtarget == None)
+ xsel.xtarget = XA_STRING;
+}
+
+int
+xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
+{
+ float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
+ ushort mode, prevmode = USHRT_MAX;
+ Font *font = &dc.font;
+ int frcflags = FRC_NORMAL;
+ float runewidth = win.cw;
+ Rune rune;
+ FT_UInt glyphidx;
+ FcResult fcres;
+ FcPattern *fcpattern, *fontpattern;
+ FcFontSet *fcsets[] = { NULL };
+ FcCharSet *fccharset;
+ int i, f, numspecs = 0;
+
+ for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
+ /* Fetch rune and mode for current glyph. */
+ rune = glyphs[i].u;
+ mode = glyphs[i].mode;
+
+ /* Skip dummy wide-character spacing. */
+ if (mode == ATTR_WDUMMY)
+ continue;
+
+ /* Determine font for glyph if different from previous glyph. */
+ if (prevmode != mode) {
+ prevmode = mode;
+ font = &dc.font;
+ frcflags = FRC_NORMAL;
+ runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
+ font = &dc.ibfont;
+ frcflags = FRC_ITALICBOLD;
+ } else if (mode & ATTR_ITALIC) {
+ font = &dc.ifont;
+ frcflags = FRC_ITALIC;
+ } else if (mode & ATTR_BOLD) {
+ font = &dc.bfont;
+ frcflags = FRC_BOLD;
+ }
+ yp = winy + font->ascent;
+ }
+
+ /* Lookup character index with default font. */
+ glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+ if (glyphidx) {
+ specs[numspecs].font = font->match;
+ specs[numspecs].glyph = glyphidx;
+ specs[numspecs].x = (short)xp;
+ specs[numspecs].y = (short)yp;
+ xp += runewidth;
+ numspecs++;
+ continue;
+ }
+
+ /* Fallback on font cache, search the font cache for match. */
+ for (f = 0; f < frclen; f++) {
+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
+ /* Everything correct. */
+ if (glyphidx && frc[f].flags == frcflags)
+ break;
+ /* We got a default font for a not found glyph. */
+ if (!glyphidx && frc[f].flags == frcflags
+ && frc[f].unicodep == rune) {
+ break;
+ }
+ }
+
+ /* Nothing was found. Use fontconfig to find matching font. */
+ if (f >= frclen) {
+ if (!font->set)
+ font->set = FcFontSort(0, font->pattern,
+ 1, 0, &fcres);
+ fcsets[0] = font->set;
+
+ /*
+ * Nothing was found in the cache. Now use
+ * some dozen of Fontconfig calls to get the
+ * font for one single character.
+ *
+ * Xft and fontconfig are design failures.
+ */
+ fcpattern = FcPatternDuplicate(font->pattern);
+ fccharset = FcCharSetCreate();
+
+ FcCharSetAddChar(fccharset, rune);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
+ fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
+
+ FcConfigSubstitute(0, fcpattern,
+ FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+
+ fontpattern = FcFontSetMatch(0, fcsets, 1,
+ fcpattern, &fcres);
+
+ /* Allocate memory for the new cache entry. */
+ if (frclen >= frccap) {
+ frccap += 16;
+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
+ }
+
+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
+ fontpattern);
+ if (!frc[frclen].font)
+ die("XftFontOpenPattern failed seeking fallback font: %s\n",
+ strerror(errno));
+ frc[frclen].flags = frcflags;
+ frc[frclen].unicodep = rune;
+
+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
+
+ f = frclen;
+ frclen++;
+
+ FcPatternDestroy(fcpattern);
+ FcCharSetDestroy(fccharset);
+ }
+
+ specs[numspecs].font = frc[f].font;
+ specs[numspecs].glyph = glyphidx;
+ specs[numspecs].x = (short)xp;
+ specs[numspecs].y = (short)yp;
+ xp += runewidth;
+ numspecs++;
+ }
+
+ return numspecs;
+}
+
+void
+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
+{
+ int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
+ int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
+ width = charlen * win.cw;
+ Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
+ XRenderColor colfg, colbg;
+ XRectangle r;
+
+ /* Fallback on color display for attributes not supported by the font */
+ if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {
+ if (dc.ibfont.badslant || dc.ibfont.badweight)
+ base.fg = defaultattr;
+ } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||
+ (base.mode & ATTR_BOLD && dc.bfont.badweight)) {
+ base.fg = defaultattr;
+ }
+
+ if (IS_TRUECOL(base.fg)) {
+ colfg.alpha = 0xffff;
+ colfg.red = TRUERED(base.fg);
+ colfg.green = TRUEGREEN(base.fg);
+ colfg.blue = TRUEBLUE(base.fg);
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg);
+ fg = &truefg;
+ } else {
+ fg = &dc.col[base.fg];
+ }
+
+ if (IS_TRUECOL(base.bg)) {
+ colbg.alpha = 0xffff;
+ colbg.green = TRUEGREEN(base.bg);
+ colbg.red = TRUERED(base.bg);
+ colbg.blue = TRUEBLUE(base.bg);
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg);
+ bg = &truebg;
+ } else {
+ bg = &dc.col[base.bg];
+ }
+
+ /* Change basic system colors [0-7] to bright system colors [8-15] */
+ if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
+ fg = &dc.col[base.fg + 8];
+
+ if (IS_SET(MODE_REVERSE)) {
+ if (fg == &dc.col[defaultfg]) {
+ fg = &dc.col[defaultbg];
+ } else {
+ colfg.red = ~fg->color.red;
+ colfg.green = ~fg->color.green;
+ colfg.blue = ~fg->color.blue;
+ colfg.alpha = fg->color.alpha;
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg,
+ &revfg);
+ fg = &revfg;
+ }
+
+ if (bg == &dc.col[defaultbg]) {
+ bg = &dc.col[defaultfg];
+ } else {
+ colbg.red = ~bg->color.red;
+ colbg.green = ~bg->color.green;
+ colbg.blue = ~bg->color.blue;
+ colbg.alpha = bg->color.alpha;
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg,
+ &revbg);
+ bg = &revbg;
+ }
+ }
+
+ if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
+ colfg.red = fg->color.red / 2;
+ colfg.green = fg->color.green / 2;
+ colfg.blue = fg->color.blue / 2;
+ colfg.alpha = fg->color.alpha;
+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
+ fg = &revfg;
+ }
+
+ if (base.mode & ATTR_REVERSE) {
+ temp = fg;
+ fg = bg;
+ bg = temp;
+ }
+
+ if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
+ fg = bg;
+
+ if (base.mode & ATTR_INVISIBLE)
+ fg = bg;
+
+ /* Intelligent cleaning up of the borders. */
+ if (x == 0) {
+ xclear(0, (y == 0)? 0 : winy, borderpx,
+ winy + win.ch +
+ ((winy + win.ch >= borderpx + win.th)? win.h : 0));
+ }
+ if (winx + width >= borderpx + win.tw) {
+ xclear(winx + width, (y == 0)? 0 : winy, win.w,
+ ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
+ }
+ if (y == 0)
+ xclear(winx, 0, winx + width, borderpx);
+ if (winy + win.ch >= borderpx + win.th)
+ xclear(winx, winy + win.ch, winx + width, win.h);
+
+ /* Clean up the region we want to draw to. */
+ XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
+
+ /* Set the clip region because Xft is sometimes dirty. */
+ r.x = 0;
+ r.y = 0;
+ r.height = win.ch;
+ r.width = width;
+ XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
+
+ /* Render the glyphs. */
+ XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
+
+ /* Render underline and strikethrough. */
+ if (base.mode & ATTR_UNDERLINE) {
+ XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * 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 = NoSymbol;
+ char buf[64], *customkey;
+ int len;
+ Rune c;
+ Status status;
+ Shortcut *bp;
+
+ if (IS_SET(MODE_KBDLOCK))
+ return;
+
+ if (xw.ime.xic) {
+ len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);
+ if (status == XBufferOverflow)
+ return;
+ } else {
+ len = XLookupString(e, buf, sizeof buf, &ksym, NULL);
+ }
+ /* 1. shortcuts */
+ for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
+ if (ksym == bp->keysym && match(bp->mod, e->state)) {
+ bp->func(&(bp->arg));
+ return;
+ }
+ }
+
+ /* 2. custom keys from config.h */
+ if ((customkey = kmap(ksym, e->state))) {
+ ttywrite(customkey, strlen(customkey), 1);
+ return;
+ }
+
+ /* 3. composed string from input method */
+ if (len == 0)
+ return;
+ if (len == 1 && e->state & Mod1Mask) {
+ if (IS_SET(MODE_8BIT)) {
+ if (*buf < 0177) {
+ c = *buf | 0x80;
+ len = utf8encode(c, buf);
+ }
+ } else {
+ buf[1] = buf[0];
+ buf[0] = '\033';
+ len = 2;
+ }
+ }
+ ttywrite(buf, len, 1);
+}
+
+void
+cmessage(XEvent *e)
+{
+ /*
+ * See xembed specs
+ * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
+ */
+ if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
+ if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
+ win.mode |= MODE_FOCUSED;
+ xseturgency(0);
+ } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
+ win.mode &= ~MODE_FOCUSED;
+ }
+ } else if (e->xclient.data.l[0] == xw.wmdeletewin) {
+ ttyhangup();
+ exit(0);
+ }
+}
+
+void
+resize(XEvent *e)
+{
+ if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
+ return;
+
+ cresize(e->xconfigure.width, e->xconfigure.height);
+}
+
+void
+run(void)
+{
+ XEvent ev;
+ int w = win.w, h = win.h;
+ fd_set rfd;
+ int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
+ struct timespec seltv, *tv, now, lastblink, trigger;
+ double timeout;
+
+ /* Waiting for window mapping */
+ do {
+ XNextEvent(xw.dpy, &ev);
+ /*
+ * This XFilterEvent call is required because of XOpenIM. It
+ * does filter out the key event and some client message for
+ * the input method too.
+ */
+ if (XFilterEvent(&ev, None))
+ continue;
+ if (ev.type == ConfigureNotify) {
+ w = ev.xconfigure.width;
+ h = ev.xconfigure.height;
+ }
+ } while (ev.type != MapNotify);
+
+ ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
+ cresize(w, h);
+
+ for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
+ FD_ZERO(&rfd);
+ FD_SET(ttyfd, &rfd);
+ FD_SET(xfd, &rfd);
+
+ if (XPending(xw.dpy))
+ timeout = 0; /* existing events might not set xfd */
+
+ seltv.tv_sec = timeout / 1E3;
+ seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
+ tv = timeout >= 0 ? &seltv : NULL;
+
+ if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
+ if (errno == EINTR)
+ continue;
+ die("select failed: %s\n", strerror(errno));
+ }
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ if (FD_ISSET(ttyfd, &rfd))
+ ttyread();
+
+ xev = 0;
+ while (XPending(xw.dpy)) {
+ xev = 1;
+ XNextEvent(xw.dpy, &ev);
+ if (XFilterEvent(&ev, None))
+ continue;
+ if (handler[ev.type])
+ (handler[ev.type])(&ev);
+ }
+
+ /*
+ * To reduce flicker and tearing, when new content or event
+ * triggers drawing, we first wait a bit to ensure we got
+ * everything, and if nothing new arrives - we draw.
+ * We start with trying to wait minlatency ms. If more content
+ * arrives sooner, we retry with shorter and shorter periods,
+ * and eventually draw even without idle after maxlatency ms.
+ * Typically this results in low latency while interacting,
+ * maximum latency intervals during `cat huge.txt`, and perfect
+ * sync with periodic updates from animations/key-repeats/etc.
+ */
+ if (FD_ISSET(ttyfd, &rfd) || xev) {
+ if (!drawing) {
+ trigger = now;
+ drawing = 1;
+ }
+ timeout = (maxlatency - TIMEDIFF(now, trigger)) \
+ / maxlatency * minlatency;
+ if (timeout > 0)
+ continue; /* we have time, try to find idle */
+ }
+
+ /* idle detected or maxlatency exhausted -> draw */
+ timeout = -1;
+ if (blinktimeout && tattrset(ATTR_BLINK)) {
+ timeout = blinktimeout - TIMEDIFF(now, lastblink);
+ if (timeout <= 0) {
+ if (-timeout > blinktimeout) /* start visible */
+ win.mode |= MODE_BLINK;
+ win.mode ^= MODE_BLINK;
+ tsetdirtattr(ATTR_BLINK);
+ lastblink = now;
+ timeout = blinktimeout;
+ }
+ }
+
+ draw();
+ XFlush(xw.dpy);
+ drawing = 0;
+ }
+}
+
+void
+usage(void)
+{
+ die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
+ " [-n name] [-o file]\n"
+ " [-T title] [-t title] [-w windowid]"
+ " [[-e] command [args ...]]\n"
+ " %s [-aiv] [-c class] [-f font] [-g geometry]"
+ " [-n name] [-o file]\n"
+ " [-T title] [-t title] [-w windowid] -l line"
+ " [stty_args ...]\n", argv0, argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ xw.l = xw.t = 0;
+ xw.isfixed = False;
+ xsetcursor(cursorshape);
+
+ ARGBEGIN {
+ case 'a':
+ allowaltscreen = 0;
+ break;
+ case 'c':
+ opt_class = EARGF(usage());
+ break;
+ case 'e':
+ if (argc > 0)
+ --argc, ++argv;
+ goto run;
+ case 'f':
+ opt_font = EARGF(usage());
+ break;
+ case 'g':
+ xw.gm = XParseGeometry(EARGF(usage()),
+ &xw.l, &xw.t, &cols, &rows);
+ break;
+ case 'i':
+ xw.isfixed = 1;
+ break;
+ case 'o':
+ opt_io = EARGF(usage());
+ break;
+ case 'l':
+ opt_line = EARGF(usage());
+ break;
+ case 'n':
+ opt_name = EARGF(usage());
+ break;
+ case 't':
+ case 'T':
+ opt_title = EARGF(usage());
+ break;
+ case 'w':
+ opt_embed = EARGF(usage());
+ break;
+ case 'v':
+ die("%s " VERSION "\n", argv0);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+run:
+ if (argc > 0) /* eat all remaining arguments */
+ opt_cmd = argv;
+
+ if (!opt_title)
+ opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0];
+
+ setlocale(LC_CTYPE, "");
+ XSetLocaleModifiers("");
+ cols = MAX(cols, 1);
+ rows = MAX(rows, 1);
+ tnew(cols, rows);
+ xinit(cols, rows);
+ xsetenv();
+ selinit();
+ run();
+
+ return 0;
+}
diff --git a/sto/x.o b/sto/x.o
new file mode 100644
index 0000000..4e5f94f
--- /dev/null
+++ b/sto/x.o
Binary files differ