aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRatakor <ratakor@disroot.org>2023-11-30 23:23:17 +0100
committerRatakor <ratakor@disroot.org>2023-11-30 23:23:17 +0100
commit6d23dcfbeea04a3f5103ef97b0db13c6b3d29171 (patch)
treeae5dfa8e00278412ea537fd9566f86916f2c8122
No Parent?
-rw-r--r--README.md4
-rw-r--r--dmenu/.gitignore4
-rw-r--r--dmenu/LICENSE31
-rw-r--r--dmenu/Makefile64
-rw-r--r--dmenu/README.md29
-rw-r--r--dmenu/arg.h49
-rw-r--r--dmenu/config.def.h60
-rw-r--r--dmenu/config.mk32
-rw-r--r--dmenu/dmenu.1208
-rw-r--r--dmenu/dmenu.c1157
-rwxr-xr-xdmenu/dmenu_path13
-rwxr-xr-xdmenu/dmenu_run13
-rw-r--r--dmenu/drw.c452
-rw-r--r--dmenu/drw.h61
-rw-r--r--dmenu/patches/dmenu-alpha-20210605-1a13d04.diff267
-rw-r--r--dmenu/patches/dmenu-border-5.2.diff37
-rw-r--r--dmenu/patches/dmenu-center-5.2.diff104
-rw-r--r--dmenu/patches/dmenu-highlight-20201211-fcdc159.diff97
-rw-r--r--dmenu/patches/dmenu-highpriority-20230221-ratakor.diff152
-rw-r--r--dmenu/patches/dmenu-mousesupport-5.2.diff144
-rw-r--r--dmenu/patches/dmenu-xresources-alt-5.0.diff182
-rw-r--r--dmenu/stest.190
-rw-r--r--dmenu/stest.c109
-rw-r--r--dmenu/util.c36
-rw-r--r--dmenu/util.h8
-rw-r--r--dwm/.gitignore3
-rw-r--r--dwm/LICENSE39
-rw-r--r--dwm/Makefile51
-rw-r--r--dwm/README.md88
-rw-r--r--dwm/config.def.h183
-rw-r--r--dwm/config.mk38
-rw-r--r--dwm/drw.c452
-rw-r--r--dwm/drw.h61
-rw-r--r--dwm/dwm.1177
-rw-r--r--dwm/dwm.c2525
-rw-r--r--dwm/dwm.pngbin0 -> 373 bytes
-rw-r--r--dwm/patches/dwm-bartab-6.4.diff117
-rw-r--r--dwm/transient.c42
-rw-r--r--dwm/util.c36
-rw-r--r--dwm/util.h8
-rw-r--r--dwmblocks/LICENSE16
-rw-r--r--dwmblocks/Makefile27
-rw-r--r--dwmblocks/README.md54
-rwxr-xr-xdwmblocks/blocks/sb_battery60
-rwxr-xr-xdwmblocks/blocks/sb_cputemp7
-rwxr-xr-xdwmblocks/blocks/sb_date4
-rwxr-xr-xdwmblocks/blocks/sb_daypercent4
-rwxr-xr-xdwmblocks/blocks/sb_ethernet4
-rwxr-xr-xdwmblocks/blocks/sb_fanspeed4
-rwxr-xr-xdwmblocks/blocks/sb_localip4
-rwxr-xr-xdwmblocks/blocks/sb_memory4
-rwxr-xr-xdwmblocks/blocks/sb_mic7
-rwxr-xr-xdwmblocks/blocks/sb_music20
-rwxr-xr-xdwmblocks/blocks/sb_news3
-rwxr-xr-xdwmblocks/blocks/sb_publicip5
-rwxr-xr-xdwmblocks/blocks/sb_time13
-rwxr-xr-xdwmblocks/blocks/sb_volume22
-rwxr-xr-xdwmblocks/blocks/sb_weather13
-rwxr-xr-xdwmblocks/blocks/sb_wifi13
-rw-r--r--dwmblocks/config.h25
-rw-r--r--dwmblocks/dwmblocks.c186
-rw-r--r--sb/LICENSE13
-rw-r--r--sb/Makefile26
-rw-r--r--sb/README.md61
-rw-r--r--sb/blocks.c504
-rw-r--r--sb/blocks.h40
-rw-r--r--sb/config.def.h39
-rw-r--r--sb/sb.c244
-rw-r--r--sb/sb.pngbin0 -> 11891 bytes
-rw-r--r--sb/util.c148
-rw-r--r--sb/util.h18
-rw-r--r--scron/LICENSE23
-rw-r--r--scron/Makefile26
-rw-r--r--scron/README33
-rw-r--r--scron/TODO2
-rw-r--r--scron/arg.h63
-rw-r--r--scron/queue.h648
-rw-r--r--scron/scron.145
-rw-r--r--scron/scron.c604
-rw-r--r--slock/.gitignore3
-rw-r--r--slock/LICENSE25
-rw-r--r--slock/Makefile61
-rw-r--r--slock/README.md31
-rw-r--r--slock/arg.h65
-rw-r--r--slock/config.def.h38
-rw-r--r--slock/config.mk40
-rw-r--r--slock/explicit_bzero.c19
-rw-r--r--slock/slock.139
-rw-r--r--slock/slock.c534
-rw-r--r--slock/util.h2
-rw-r--r--st/.gitignore3
-rw-r--r--st/FAQ253
-rw-r--r--st/LEGACY17
-rw-r--r--st/LICENSE35
-rw-r--r--st/Makefile57
-rw-r--r--st/README.md53
-rw-r--r--st/TODO28
-rw-r--r--st/arg.h50
-rw-r--r--st/config.def.h500
-rw-r--r--st/config.mk37
-rwxr-xr-xst/plumber222
-rw-r--r--st/st-plumber-0.9.diff36
-rw-r--r--st/st.1185
-rw-r--r--st/st.c2846
-rw-r--r--st/st.h131
-rw-r--r--st/st.info239
-rw-r--r--st/win.h43
-rw-r--r--st/x.c2391
108 files changed, 18238 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..05d278a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+# Ratakor's suckless softwares
+
+Still useable but not maintained, more information on each software in their
+respective folder.
diff --git a/dmenu/.gitignore b/dmenu/.gitignore
new file mode 100644
index 0000000..d29fa65
--- /dev/null
+++ b/dmenu/.gitignore
@@ -0,0 +1,4 @@
+*.o
+dmenu
+stest
+config.h
diff --git a/dmenu/LICENSE b/dmenu/LICENSE
new file mode 100644
index 0000000..0761e48
--- /dev/null
+++ b/dmenu/LICENSE
@@ -0,0 +1,31 @@
+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>
+© 2022-2023 Ratakor <ratakor@disroot.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/dmenu/Makefile b/dmenu/Makefile
new file mode 100644
index 0000000..a03a95c
--- /dev/null
+++ b/dmenu/Makefile
@@ -0,0 +1,64 @@
+# dmenu - dynamic menu
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = drw.c dmenu.c stest.c util.c
+OBJ = $(SRC:.c=.o)
+
+all: options dmenu stest
+
+options:
+ @echo dmenu build options:
+ @echo "CFLAGS = $(CFLAGS)"
+ @echo "LDFLAGS = $(LDFLAGS)"
+ @echo "CC = $(CC)"
+
+.c.o:
+ $(CC) -c $(CFLAGS) $<
+
+config.h:
+ cp config.def.h $@
+
+$(OBJ): arg.h config.h config.mk drw.h
+
+dmenu: dmenu.o drw.o util.o
+ $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS)
+
+stest: stest.o
+ $(CC) -o $@ stest.o $(LDFLAGS)
+
+clean:
+ rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz
+
+dist: clean
+ mkdir -p dmenu-$(VERSION)
+ cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\
+ drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\
+ dmenu-$(VERSION)
+ tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION)
+ gzip dmenu-$(VERSION).tar
+ rm -rf dmenu-$(VERSION)
+
+install: all
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/stest
+ mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1
+ sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1
+ chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1
+ chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1
+
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\
+ $(DESTDIR)$(PREFIX)/bin/dmenu_path\
+ $(DESTDIR)$(PREFIX)/bin/dmenu_run\
+ $(DESTDIR)$(PREFIX)/bin/stest\
+ $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\
+ $(DESTDIR)$(MANPREFIX)/man1/stest.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/dmenu/README.md b/dmenu/README.md
new file mode 100644
index 0000000..189fa5f
--- /dev/null
+++ b/dmenu/README.md
@@ -0,0 +1,29 @@
+# Ratakor's build of dmenu
+- Patches added in 'patches' directory but there was some modification made to
+them
+- uses Xresource and default to Dracula theme if Xresource is not set
+
+dmenu - dynamic menu
+====================
+dmenu is an efficient dynamic menu for X.
+
+
+Requirements
+------------
+In order to build dmenu you need the Xlib header files.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (dmenu is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install dmenu
+(if necessary as root):
+
+ make clean install
+
+
+Running dmenu
+-------------
+See the man page for details.
diff --git a/dmenu/arg.h b/dmenu/arg.h
new file mode 100644
index 0000000..e94e02b
--- /dev/null
+++ b/dmenu/arg.h
@@ -0,0 +1,49 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][0] == '-'\
+ && argv[0][1];\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/dmenu/config.def.h b/dmenu/config.def.h
new file mode 100644
index 0000000..046de88
--- /dev/null
+++ b/dmenu/config.def.h
@@ -0,0 +1,60 @@
+/* See LICENSE file for copyright and license details. */
+/* Default settings; can be overriden by command line. */
+
+static int topbar = 0; /* -b option; if 0, dmenu appears at bottom */
+static int centered = 1; /* -c option; centers dmenu on screen */
+static int min_width = 450; /* minimum width when centered */
+
+/* -fn option overrides fonts[0]; default X11 font or font set */
+static char font[] = "monospace:size=12";
+static const char *fonts[] = {
+ font,
+ "monospace:size=12"
+};
+static const char *prompt = NULL; /* -p option; prompt to the left of input field */
+static char normfgcolor[] = "#f8f8f2";
+static char normbgcolor[] = "#282a36";
+static char selfgcolor[] = "#f8f8f2";
+static char selbgcolor[] = "#6272a4";
+static char outcolor[] = "#bd93f9";
+static char highlightcolor[] = "#ffb86c";
+static char *colors[SchemeLast][2] = {
+ /* fg bg */
+ [SchemeNorm] = { normfgcolor, normbgcolor },
+ [SchemeSel] = { selfgcolor, selbgcolor },
+ [SchemeOut] = { outcolor, normbgcolor },
+ [SchemeNormHighlight] = { highlightcolor, normbgcolor },
+ [SchemeSelHighlight] = { highlightcolor, selbgcolor },
+};
+static const unsigned int alphas[SchemeLast][2] = {
+ [SchemeNorm] = { OPAQUE, 0xd9 },
+ [SchemeSel] = { OPAQUE, 0xd9 },
+ [SchemeOut] = { OPAQUE, 0xd9 },
+ [SchemeNormHighlight] = { OPAQUE, 0xd9 },
+ [SchemeSelHighlight] = { OPAQUE, 0xd9 },
+};
+
+/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
+static unsigned int lines = 12;
+
+/*
+ * Characters not considered part of a word while deleting words
+ * for example: " /?\"&[]"
+ */
+static const char worddelimiters[] = " ";
+
+/* Size of the window border */
+static unsigned int border_width = 1;
+
+/*
+ * Xresources preferences to load at startup
+ */
+ResourcePref resources[] = {
+ { "font", STRING, &font },
+ { "normfgcolor", STRING, &normfgcolor },
+ { "normbgcolor", STRING, &normbgcolor },
+ { "selfgcolor", STRING, &selfgcolor },
+ { "selbgcolor", STRING, &selbgcolor },
+ { "color4", STRING, &outcolor },
+ { "highlight", STRING, &highlightcolor },
+};
diff --git a/dmenu/config.mk b/dmenu/config.mk
new file mode 100644
index 0000000..528874b
--- /dev/null
+++ b/dmenu/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) -lXrender
+
+# 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 -O2 $(INCS) $(CPPFLAGS)
+LDFLAGS += $(LIBS)
+
+# compiler and linker
+CC = cc
diff --git a/dmenu/dmenu.1 b/dmenu/dmenu.1
new file mode 100644
index 0000000..3373129
--- /dev/null
+++ b/dmenu/dmenu.1
@@ -0,0 +1,208 @@
+.TH DMENU 1 dmenu\-VERSION
+.SH NAME
+dmenu \- dynamic menu
+.SH SYNOPSIS
+.B dmenu
+.RB [ \-bcfiv ]
+.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 [ \-hf
+.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 \-c
+dmenu appears centered on 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 \-hp " items"
+defines high priority items, best to use with dmenu_run.
+.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
+.BI \-hf " color"
+defines the highlight 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\-V
+Paste from primary X selection
+.TP
+.B C\-Y
+Paste from X clipboard
+.TP
+.B M\-b
+Move cursor to the start of the current word
+.TP
+.B M\-f
+Move cursor to the end of the current word
+.TP
+.B M\-g
+Home
+.TP
+.B M\-G
+End
+.TP
+.B M\-h
+Up
+.TP
+.B M\-j
+Page down
+.TP
+.B M\-k
+Page up
+.TP
+.B M\-l
+Down
+.SH SEE ALSO
+.IR dwm (1),
+.IR stest (1)
diff --git a/dmenu/dmenu.c b/dmenu/dmenu.c
new file mode 100644
index 0000000..9a0a344
--- /dev/null
+++ b/dmenu/dmenu.c
@@ -0,0 +1,1157 @@
+/* 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/Xproto.h>
+#include <X11/Xutil.h>
+#include <X11/Xresource.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)
+
+#define OPAQUE 0xffU
+
+/* enums */
+enum { SchemeNorm, SchemeSel, SchemeOut, SchemeNormHighlight, SchemeSelHighlight, SchemeLast }; /* color schemes */
+struct item {
+ char *text;
+ struct item *left, *right;
+ int out, hp;
+};
+
+static char **hpitems = NULL;
+static int hplength = 0;
+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];
+
+/* Xresources preferences */
+enum resource_type {
+ STRING = 0,
+ INTEGER = 1,
+ FLOAT = 2
+};
+typedef struct {
+ char *name;
+ enum resource_type type;
+ void *dst;
+} ResourcePref;
+
+static void load_xresources(void);
+static void resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst);
+
+static int useargb = 0;
+static Visual *visual;
+static int depth;
+static Colormap cmap;
+
+#include "config.h"
+
+static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+static char *(*fstrstr)(const char *, const char *) = strstr;
+static void xinitvisual(void);
+
+static char **
+tokenize(char *source, const char *delim, int *llen)
+{
+ int listlength = 0, list_size = 0;
+ char **list = NULL, *token;
+
+ token = strtok(source, delim);
+ while (token) {
+ if (listlength + 1 >= list_size) {
+ if (!(list = realloc(list, (list_size += 8) * sizeof(*list))))
+ die("Unable to realloc %zu bytes\n", list_size * sizeof(*list));
+ }
+ if (!(list[listlength] = strdup(token)))
+ die("Unable to strdup %zu bytes\n", strlen(token) + 1);
+ token = strtok(NULL, delim);
+ listlength++;
+ }
+
+ *llen = listlength;
+ return list;
+}
+
+static int
+arrayhas(char **list, int length, char *item)
+{
+ int i;
+
+ for (i = 0; i < length; i++) {
+ size_t len1 = strlen(list[i]);
+ size_t len2 = strlen(item);
+ if (!fstrncmp(list[i], item, len1 > len2 ? len2 : len1))
+ return 1;
+ }
+ return 0;
+}
+
+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 int
+max_textw(void)
+{
+ int len = 0;
+ for (struct item *item = items; item && item->text; item++)
+ len = MAX(TEXTW(item->text), len);
+ return len;
+}
+
+static void
+cleanup(void)
+{
+ size_t i;
+
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ for (i = 0; i < SchemeLast; i++)
+ free(scheme[i]);
+ for (i = 0; i < hplength; ++i)
+ free(hpitems[i]);
+ free(hpitems);
+ 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 void
+drawhighlights(struct item *item, int x, int y, int maxw)
+{
+ char restorechar, tokens[sizeof text], *highlight, *token;
+ int indentx, highlightlen;
+
+ drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : item->out ? SchemeOut : SchemeNormHighlight]);
+ strcpy(tokens, text);
+ for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) {
+ highlight = fstrstr(item->text, token);
+ while (highlight) {
+ /* Move item str end, calc width for highlight indent, & restore */
+ highlightlen = highlight - item->text;
+ restorechar = *highlight;
+ item->text[highlightlen] = '\0';
+ indentx = TEXTW(item->text);
+ item->text[highlightlen] = restorechar;
+
+ /* Move highlight str end, draw highlight, & restore */
+ restorechar = highlight[strlen(token)];
+ highlight[strlen(token)] = '\0';
+ if (indentx - (lrpad / 2) < maxw)
+ drw_text(
+ drw, x + indentx - (lrpad / 2), y,
+ MIN(maxw - indentx,
+ TEXTW(highlight) - lrpad),
+ bh, 0, highlight, 0
+ );
+ highlight[strlen(token)] = restorechar;
+
+ if (strlen(highlight) - strlen(token) < strlen(token)) break;
+ highlight = fstrstr(highlight + strlen(token), token);
+ }
+ }
+}
+
+static int
+drawitem(struct item *item, int x, int y, int w)
+{
+ if (item == sel)
+ drw_setscheme(drw, scheme[SchemeSel]);
+ else
+ drw_setscheme(drw, scheme[SchemeNorm]);
+
+ int r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
+ drawhighlights(item, x, y, w);
+ return r;
+}
+
+static void
+drawmenu(void)
+{
+ unsigned int curpos;
+ struct item *item;
+ int x = 0, y = 0, w;
+
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_rect(drw, 0, 0, mw, mh, 1, 1);
+
+ if (prompt && *prompt) {
+ drw_setscheme(drw, scheme[SchemeSel]);
+ x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
+ }
+ /* draw input field */
+ w = (lines > 0 || !matches) ? mw - x : inputw;
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
+
+ curpos = TEXTW(text) - TEXTW(&text[cursor]);
+ if ((curpos += lrpad / 2 - 1) < w) {
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
+ }
+
+ if (lines > 0) {
+ /* draw 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, *lhpprefix, *lprefix, *lsubstr, *hpprefixend, *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 = lhpprefix = lprefix = lsubstr = matchend = hpprefixend = 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 (item->hp && !fstrncmp(tokv[0], item->text, len))
+ appenditem(item, &lhpprefix, &hpprefixend);
+ else if (!fstrncmp(tokv[0], item->text, len))
+ appenditem(item, &lprefix, &prefixend);
+ else
+ appenditem(item, &lsubstr, &substrend);
+ }
+ if (lhpprefix) {
+ if (matches) {
+ matchend->right = lhpprefix;
+ lhpprefix->left = matchend;
+ } else
+ matches = lhpprefix;
+ matchend = hpprefixend;
+ }
+ 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_V:
+ 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
+buttonpress(XEvent *e)
+{
+ struct item *item;
+ XButtonPressedEvent *ev = &e->xbutton;
+ int x = 0, y = 0, h = bh, w;
+
+ if (ev->window != win)
+ return;
+
+ /* right-click: exit */
+ if (ev->button == Button3)
+ exit(1);
+
+ if (prompt && *prompt)
+ x += promptw;
+
+ /* input field */
+ w = (lines > 0 || !matches) ? mw - x : inputw;
+
+ /* left-click on input: clear input,
+ * NOTE: if there is no left-arrow the space for < is reserved so
+ * add that to the input width */
+ if (ev->button == Button1 &&
+ ((lines <= 0 && ev->x >= 0 && ev->x <= x + w +
+ ((!prev || !curr->left) ? TEXTW("<") : 0)) ||
+ (lines > 0 && ev->y >= y && ev->y <= y + h))) {
+ insert(NULL, -cursor);
+ drawmenu();
+ return;
+ }
+ /* middle-mouse click: paste selection */
+ if (ev->button == Button2) {
+ XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
+ utf8, utf8, win, CurrentTime);
+ drawmenu();
+ return;
+ }
+ /* scroll up */
+ if (ev->button == Button4 && prev) {
+ sel = curr = prev;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ /* scroll down */
+ if (ev->button == Button5 && next) {
+ sel = curr = next;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ if (ev->button != Button1)
+ return;
+ if (ev->state & ~ControlMask)
+ return;
+ if (lines > 0) {
+ /* vertical list: (ctrl)left-click on item */
+ w = mw - x;
+ for (item = curr; item != next; item = item->right) {
+ y += h;
+ if (ev->y >= y && ev->y <= (y + h)) {
+ puts(item->text);
+ if (!(ev->state & ControlMask))
+ exit(0);
+ sel = item;
+ if (sel) {
+ sel->out = 1;
+ drawmenu();
+ }
+ return;
+ }
+ }
+ } else if (matches) {
+ /* left-click on left arrow */
+ x += inputw;
+ w = TEXTW("<");
+ if (prev && curr->left) {
+ if (ev->x >= x && ev->x <= x + w) {
+ sel = curr = prev;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ }
+ /* horizontal list: (ctrl)left-click on item */
+ for (item = curr; item != next; item = item->right) {
+ x += w;
+ w = MIN(TEXTW(item->text), mw - x - TEXTW(">"));
+ if (ev->x >= x && ev->x <= x + w) {
+ puts(item->text);
+ if (!(ev->state & ControlMask))
+ exit(0);
+ sel = item;
+ if (sel) {
+ sel->out = 1;
+ drawmenu();
+ }
+ return;
+ }
+ }
+ /* left-click on right arrow */
+ w = TEXTW(">");
+ x = mw - w;
+ if (next && ev->x >= x && ev->x <= x + w) {
+ sel = curr = next;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ }
+}
+
+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;
+ items[i].hp = arrayhas(hpitems, hplength, items[i].text);
+ line = NULL; /* next call of getline() allocates a new line */
+ }
+ free(line);
+ if (items)
+ items[i].text = NULL;
+ lines = MIN(lines, i);
+}
+
+void
+resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
+{
+ char *sdst = NULL;
+ int *idst = NULL;
+ float *fdst = NULL;
+ sdst = dst;
+ idst = dst;
+ fdst = dst;
+ char fullname[256];
+ char *type;
+ XrmValue ret;
+ snprintf(fullname, sizeof(fullname), "%s.%s", "dmenu", name);
+ fullname[sizeof(fullname) - 1] = '\0';
+ XrmGetResource(db, fullname, "*", &type, &ret);
+ if (!(ret.addr == NULL || strncmp("String", type, 64)))
+ {
+ switch (rtype) {
+ case STRING:
+ strcpy(sdst, ret.addr);
+ break;
+ case INTEGER:
+ *idst = strtoul(ret.addr, NULL, 10);
+ break;
+ case FLOAT:
+ *fdst = strtof(ret.addr, NULL);
+ break;
+ }
+ }
+}
+
+void
+load_xresources(void)
+{
+ Display *display;
+ char *resm;
+ XrmDatabase db;
+ ResourcePref *p;
+ display = XOpenDisplay(NULL);
+ resm = XResourceManagerString(display);
+ if (!resm)
+ return;
+ db = XrmGetStringDatabase(resm);
+ for (p = resources; p < resources + LENGTH(resources); p++)
+ resource_load(db, p->name, p->type, p->dst);
+ XCloseDisplay(display);
+}
+
+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 ButtonPress:
+ buttonpress(&ev);
+ break;
+ 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 = 0, 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], alphas[i], 2);
+
+ clip = XInternAtom(dpy, "CLIPBOARD", False);
+ utf8 = XInternAtom(dpy, "UTF8_STRING", False);
+
+ /* calculate menu geometry */
+ bh = drw->fonts->h + 2;
+ lines = MAX(lines, 0);
+ mh = (lines + 1) * bh;
+ promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
+#ifdef XINERAMA
+ i = 0;
+ if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
+ XGetInputFocus(dpy, &w, &di);
+ if (mon >= 0 && mon < n)
+ i = mon;
+ else if (w != root && w != PointerRoot && w != None) {
+ /* find top-level window containing current input focus */
+ do {
+ if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws)
+ XFree(dws);
+ } while (w != root && w != pw);
+ /* find xinerama screen with which the window intersects most */
+ if (XGetWindowAttributes(dpy, pw, &wa))
+ for (j = 0; j < n; j++)
+ if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) {
+ area = a;
+ i = j;
+ }
+ }
+ /* no focused window is on screen, so use pointer location instead */
+ if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
+ for (i = 0; i < n; i++)
+ if (INTERSECT(x, y, 1, 1, info[i]) != 0)
+ break;
+
+ if (centered) {
+ mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width);
+ x = info[i].x_org + ((info[i].width - mw) / 2);
+ y = info[i].y_org + ((info[i].height - mh) / 2);
+ } else {
+ 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);
+
+ if (centered) {
+ mw = MIN(MAX(max_textw() + promptw, min_width), wa.width);
+ x = (wa.width - mw) / 2;
+ y = (wa.height - mh) / 2;
+ } else {
+ 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 = 0;
+ swa.border_pixel = 0;
+ swa.colormap = cmap;
+ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask;
+ win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
+ depth, CopyFromParent, visual,
+ CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &swa);
+ if (border_width)
+ XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel);
+ XSetClassHint(dpy, win, &ch);
+
+
+ /* input methods */
+ if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
+ die("XOpenIM failed: could not open input device");
+
+ xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
+ XNClientWindow, win, XNFocusWindow, win, NULL);
+
+ XMapRaised(dpy, win);
+ if (embed) {
+ 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 [-bcfiv] [-l lines] [-p prompt] [-fn font] [-m monitor] [-hp items]\n"
+ " [-nb color] [-nf color] [-sb color] [-sf color] [-hf color] [-w windowid]", stderr);
+}
+
+int
+main(int argc, char *argv[])
+{
+ XWindowAttributes wa;
+ int i, fast = 0;
+
+ if (!(dpy = XOpenDisplay(NULL)))
+ die("cannot open display");
+
+ XrmInitialize();
+ load_xresources();
+
+ 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], "-c")) /* centers dmenu on screen */
+ centered = 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];
+ colors[SchemeOut][ColBg] = argv[i];
+ colors[SchemeNormHighlight][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];
+ colors[SchemeSelHighlight][ColBg] = argv[i];
+ }
+ else if (!strcmp(argv[i], "-sf")) /* selected foreground color */
+ colors[SchemeSel][ColFg] = argv[++i];
+ else if (!strcmp(argv[i], "-hf")) { /* highlight foreground color */
+ colors[SchemeNormHighlight][ColFg] = argv[++i];
+ colors[SchemeSelHighlight][ColFg] = argv[i];
+ }
+ else if (!strcmp(argv[i], "-w")) /* embedding window id */
+ embed = argv[++i];
+ else if (!strcmp(argv[i], "-hp"))
+ hpitems = tokenize(argv[++i], ",", &hplength);
+ else if (!strcmp(argv[i], "-bw"))
+ border_width = atoi(argv[++i]); /* border width */
+ else
+ usage();
+
+ if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
+ fputs("warning: no locale support\n", stderr);
+ 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);
+ xinitvisual();
+ drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap);
+ 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 */
+}
+
+ void
+xinitvisual(void)
+{
+ XVisualInfo *infos;
+ XRenderPictFormat *fmt;
+ int nitems;
+ int i;
+
+ XVisualInfo tpl = {
+ .screen = screen,
+ .depth = 32,
+ .class = TrueColor
+ };
+ long masks = VisualScreenMask | VisualDepthMask | VisualClassMask;
+
+ infos = XGetVisualInfo(dpy, masks, &tpl, &nitems);
+ visual = NULL;
+ for(i = 0; i < nitems; i ++) {
+ fmt = XRenderFindVisualFormat(dpy, infos[i].visual);
+ if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) {
+ visual = infos[i].visual;
+ depth = infos[i].depth;
+ cmap = XCreateColormap(dpy, root, visual, AllocNone);
+ useargb = 1;
+ break;
+ }
+ }
+
+ XFree(infos);
+
+ if (! visual) {
+ visual = DefaultVisual(dpy, screen);
+ depth = DefaultDepth(dpy, screen);
+ cmap = DefaultColormap(dpy, screen);
+ }
+}
diff --git a/dmenu/dmenu_path b/dmenu/dmenu_path
new file mode 100755
index 0000000..3a7cda7
--- /dev/null
+++ b/dmenu/dmenu_path
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}"
+cache="$cachedir/dmenu_run"
+
+[ ! -e "$cachedir" ] && mkdir -p "$cachedir"
+
+IFS=:
+if stest -dqr -n "$cache" $PATH; then
+ stest -flx $PATH | sort -u | tee "$cache"
+else
+ cat "$cache"
+fi
diff --git a/dmenu/dmenu_run b/dmenu/dmenu_run
new file mode 100755
index 0000000..c25d1a3
--- /dev/null
+++ b/dmenu/dmenu_run
@@ -0,0 +1,13 @@
+#!/bin/sh
+# command ending with '!', is started in the terminal.
+
+cmd="$(dmenu_path | dmenu "$@")"
+
+[ -z "$cmd" ] && exit 1
+
+case $cmd in
+*\!)
+ "${TERMINAL:-st}" -e "${cmd%?}" & ;;
+*)
+ "${cmd}" & ;;
+esac
diff --git a/dmenu/drw.c b/dmenu/drw.c
new file mode 100644
index 0000000..f02fb49
--- /dev/null
+++ b/dmenu/drw.c
@@ -0,0 +1,452 @@
+/* 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, Visual *visual, unsigned int depth, Colormap cmap)
+{
+ Drw *drw = ecalloc(1, sizeof(Drw));
+
+ drw->dpy = dpy;
+ drw->screen = screen;
+ drw->root = root;
+ drw->w = w;
+ drw->h = h;
+ drw->visual = visual;
+ drw->depth = depth;
+ drw->cmap = cmap;
+ drw->drawable = XCreatePixmap(dpy, root, w, h, depth);
+ drw->gc = XCreateGC(dpy, drw->drawable, 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, drw->depth);
+}
+
+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, unsigned int alpha)
+{
+ if (!drw || !dest || !clrname)
+ return;
+
+ if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap,
+ clrname, dest))
+ die("error, cannot allocate color '%s'", clrname);
+
+ dest->pixel = (dest->pixel & 0x00ffffffU) | (alpha << 24);
+}
+
+/* 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, char *clrnames[], const unsigned int alphas[], 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], alphas[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, drw->visual, drw->cmap);
+ 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/dmenu/drw.h b/dmenu/drw.h
new file mode 100644
index 0000000..31f5b23
--- /dev/null
+++ b/dmenu/drw.h
@@ -0,0 +1,61 @@
+/* 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;
+ Visual *visual;
+ unsigned int depth;
+ Colormap cmap;
+ 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, Visual*, unsigned int, Colormap);
+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, unsigned int alpha);
+Clr *drw_scm_create(Drw *drw, char *clrnames[], const unsigned int alphas[], size_t clrcount);
+
+/* Cursor abstraction */
+Cur *drw_cur_create(Drw *drw, int shape);
+void drw_cur_free(Drw *drw, Cur *cursor);
+
+/* Drawing context manipulation */
+void drw_setfontset(Drw *drw, Fnt *set);
+void drw_setscheme(Drw *drw, Clr *scm);
+
+/* Drawing functions */
+void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
+int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
+
+/* Map functions */
+void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
diff --git a/dmenu/patches/dmenu-alpha-20210605-1a13d04.diff b/dmenu/patches/dmenu-alpha-20210605-1a13d04.diff
new file mode 100644
index 0000000..51679f7
--- /dev/null
+++ b/dmenu/patches/dmenu-alpha-20210605-1a13d04.diff
@@ -0,0 +1,267 @@
+diff --git a/config.def.h b/config.def.h
+index 1edb647..697d511 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -2,6 +2,7 @@
+ /* Default settings; can be overriden by command line. */
+
+ static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
++static const unsigned int alpha = 0xf0;
+ /* -fn option overrides fonts[0]; default X11 font or font set */
+ static const char *fonts[] = {
+ "monospace:size=10"
+@@ -13,6 +14,13 @@ static const char *colors[SchemeLast][2] = {
+ [SchemeSel] = { "#eeeeee", "#005577" },
+ [SchemeOut] = { "#000000", "#00ffff" },
+ };
++
++static const unsigned int alphas[SchemeLast][2] = {
++ [SchemeNorm] = { OPAQUE, alpha },
++ [SchemeSel] = { OPAQUE, alpha },
++ [SchemeOut] = { OPAQUE, alpha },
++};
++
+ /* -l option; if nonzero, dmenu uses vertical list with given number of lines */
+ static unsigned int lines = 0;
+
+diff --git a/dmenu.c b/dmenu.c
+index 65f25ce..3e56e1a 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -10,6 +10,7 @@
+
+ #include <X11/Xlib.h>
+ #include <X11/Xatom.h>
++#include <X11/Xproto.h>
+ #include <X11/Xutil.h>
+ #ifdef XINERAMA
+ #include <X11/extensions/Xinerama.h>
+@@ -25,6 +26,8 @@
+ #define LENGTH(X) (sizeof X / sizeof X[0])
+ #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
++#define OPAQUE 0xffU
++
+ /* enums */
+ enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
+
+@@ -53,10 +56,16 @@ static XIC xic;
+ static Drw *drw;
+ static Clr *scheme[SchemeLast];
+
++static int useargb = 0;
++static Visual *visual;
++static int depth;
++static Colormap cmap;
++
+ #include "config.h"
+
+ static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+ static char *(*fstrstr)(const char *, const char *) = strstr;
++static void xinitvisual();
+
+ static void
+ appenditem(struct item *item, struct item **list, struct item **last)
+@@ -602,7 +611,7 @@ setup(void)
+ #endif
+ /* init appearance */
+ for (j = 0; j < SchemeLast; j++)
+- scheme[j] = drw_scm_create(drw, colors[j], 2);
++ scheme[j] = drw_scm_create(drw, colors[j], alphas[i], 2);
+
+ clip = XInternAtom(dpy, "CLIPBOARD", False);
+ utf8 = XInternAtom(dpy, "UTF8_STRING", False);
+@@ -640,6 +649,7 @@ setup(void)
+ x = info[i].x_org;
+ y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
+ mw = info[i].width;
++
+ XFree(info);
+ } else
+ #endif
+@@ -657,11 +667,13 @@ setup(void)
+
+ /* create menu window */
+ swa.override_redirect = True;
+- swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
++ swa.background_pixel = 0;
++ swa.border_pixel = 0;
++ swa.colormap = cmap;
+ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
+- win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
+- CopyFromParent, CopyFromParent, CopyFromParent,
+- CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
++ win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
++ depth, CopyFromParent, visual,
++ CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &swa);
+ XSetClassHint(dpy, win, &ch);
+
+
+@@ -747,7 +759,8 @@ main(int argc, char *argv[])
+ 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);
++ xinitvisual();
++ drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap);
+ if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+@@ -769,3 +782,40 @@ main(int argc, char *argv[])
+
+ return 1; /* unreachable */
+ }
++
++ void
++xinitvisual()
++{
++ XVisualInfo *infos;
++ XRenderPictFormat *fmt;
++ int nitems;
++ int i;
++
++ XVisualInfo tpl = {
++ .screen = screen,
++ .depth = 32,
++ .class = TrueColor
++ };
++ long masks = VisualScreenMask | VisualDepthMask | VisualClassMask;
++
++ infos = XGetVisualInfo(dpy, masks, &tpl, &nitems);
++ visual = NULL;
++ for(i = 0; i < nitems; i ++) {
++ fmt = XRenderFindVisualFormat(dpy, infos[i].visual);
++ if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) {
++ visual = infos[i].visual;
++ depth = infos[i].depth;
++ cmap = XCreateColormap(dpy, root, visual, AllocNone);
++ useargb = 1;
++ break;
++ }
++ }
++
++ XFree(infos);
++
++ if (! visual) {
++ visual = DefaultVisual(dpy, screen);
++ depth = DefaultDepth(dpy, screen);
++ cmap = DefaultColormap(dpy, screen);
++ }
++}
+diff --git a/drw.c b/drw.c
+index 4cdbcbe..fe3aadd 100644
+--- a/drw.c
++++ b/drw.c
+@@ -61,7 +61,7 @@ utf8decode(const char *c, long *u, size_t clen)
+ }
+
+ Drw *
+-drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
++drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap)
+ {
+ Drw *drw = ecalloc(1, sizeof(Drw));
+
+@@ -70,8 +70,11 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h
+ 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);
++ drw->visual = visual;
++ drw->depth = depth;
++ drw->cmap = cmap;
++ drw->drawable = XCreatePixmap(dpy, root, w, h, depth);
++ drw->gc = XCreateGC(dpy, drw->drawable, 0, NULL);
+ XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
+
+ return drw;
+@@ -87,7 +90,7 @@ drw_resize(Drw *drw, unsigned int w, unsigned int h)
+ 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));
++ drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, drw->depth);
+ }
+
+ void
+@@ -194,21 +197,22 @@ drw_fontset_free(Fnt *font)
+ }
+
+ void
+-drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
++drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha)
+ {
+ if (!drw || !dest || !clrname)
+ return;
+
+- if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
+- DefaultColormap(drw->dpy, drw->screen),
++ if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap,
+ clrname, dest))
+ die("error, cannot allocate color '%s'", clrname);
++
++ dest->pixel = (dest->pixel & 0x00ffffffU) | (alpha << 24);
+ }
+
+ /* 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)
++drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount)
+ {
+ size_t i;
+ Clr *ret;
+@@ -218,7 +222,7 @@ drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
+ return NULL;
+
+ for (i = 0; i < clrcount; i++)
+- drw_clr_create(drw, &ret[i], clrnames[i]);
++ drw_clr_create(drw, &ret[i], clrnames[i], alphas[i]);
+ return ret;
+ }
+
+@@ -274,9 +278,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
+ } 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));
++ d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap);
+ x += lpad;
+ w -= lpad;
+ }
+diff --git a/drw.h b/drw.h
+index 4c67419..f6fa5cd 100644
+--- a/drw.h
++++ b/drw.h
+@@ -20,6 +20,9 @@ typedef struct {
+ Display *dpy;
+ int screen;
+ Window root;
++ Visual *visual;
++ unsigned int depth;
++ Colormap cmap;
+ Drawable drawable;
+ GC gc;
+ Clr *scheme;
+@@ -27,7 +30,7 @@ typedef struct {
+ } Drw;
+
+ /* Drawable abstraction */
+-Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h);
++Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h, Visual*, unsigned int, Colormap);
+ void drw_resize(Drw *drw, unsigned int w, unsigned int h);
+ void drw_free(Drw *drw);
+
+@@ -38,8 +41,8 @@ unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
+ void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
+
+ /* Colorscheme abstraction */
+-void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
+-Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
++void drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha);
++Clr *drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount);
+
+ /* Cursor abstraction */
+ Cur *drw_cur_create(Drw *drw, int shape);
diff --git a/dmenu/patches/dmenu-border-5.2.diff b/dmenu/patches/dmenu-border-5.2.diff
new file mode 100644
index 0000000..515c3c8
--- /dev/null
+++ b/dmenu/patches/dmenu-border-5.2.diff
@@ -0,0 +1,37 @@
+diff --git a/config.def.h b/config.def.h
+index 1edb647..dd3eb31 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -21,3 +21,6 @@ static unsigned int lines = 0;
+ * for example: " /?\"&[]"
+ */
+ static const char worddelimiters[] = " ";
++
++/* Size of the window border */
++static unsigned int border_width = 0;
+diff --git a/dmenu.c b/dmenu.c
+index 27b7a30..7c130fc 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -684,9 +684,11 @@ setup(void)
+ swa.override_redirect = True;
+ swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
+ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
+- win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
++ win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
+ CopyFromParent, CopyFromParent, CopyFromParent,
+ CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
++ if (border_width)
++ XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel);
+ XSetClassHint(dpy, win, &ch);
+
+
+@@ -757,6 +759,8 @@ main(int argc, char *argv[])
+ colors[SchemeSel][ColFg] = argv[++i];
+ else if (!strcmp(argv[i], "-w")) /* embedding window id */
+ embed = argv[++i];
++ else if (!strcmp(argv[i], "-bw"))
++ border_width = atoi(argv[++i]); /* border width */
+ else
+ usage();
+
diff --git a/dmenu/patches/dmenu-center-5.2.diff b/dmenu/patches/dmenu-center-5.2.diff
new file mode 100644
index 0000000..9401dc5
--- /dev/null
+++ b/dmenu/patches/dmenu-center-5.2.diff
@@ -0,0 +1,104 @@
+diff --git a/config.def.h b/config.def.h
+index 1edb647..88ef264 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -2,6 +2,8 @@
+ /* Default settings; can be overriden by command line. */
+
+ static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
++static int centered = 0; /* -c option; centers dmenu on screen */
++static int min_width = 500; /* minimum width when centered */
+ /* -fn option overrides fonts[0]; default X11 font or font set */
+ static const char *fonts[] = {
+ "monospace:size=10"
+diff --git a/dmenu.1 b/dmenu.1
+index 323f93c..c036baa 100644
+--- a/dmenu.1
++++ b/dmenu.1
+@@ -40,6 +40,9 @@ which lists programs in the user's $PATH and runs the result in their $SHELL.
+ .B \-b
+ dmenu appears at the bottom of the screen.
+ .TP
++.B \-c
++dmenu appears centered on 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.
+diff --git a/dmenu.c b/dmenu.c
+index 27b7a30..427fb04 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -96,6 +96,15 @@ calcoffsets(void)
+ break;
+ }
+
++static int
++max_textw(void)
++{
++ int len = 0;
++ for (struct item *item = items; item && item->text; item++)
++ len = MAX(TEXTW(item->text), len);
++ return len;
++}
++
+ static void
+ cleanup(void)
+ {
+@@ -636,6 +645,7 @@ setup(void)
+ bh = drw->fonts->h + 2;
+ lines = MAX(lines, 0);
+ mh = (lines + 1) * bh;
++ promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
+ #ifdef XINERAMA
+ i = 0;
+ if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
+@@ -662,9 +672,16 @@ setup(void)
+ 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;
++ if (centered) {
++ mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width);
++ x = info[i].x_org + ((info[i].width - mw) / 2);
++ y = info[i].y_org + ((info[i].height - mh) / 2);
++ } else {
++ x = info[i].x_org;
++ y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
++ mw = info[i].width;
++ }
++
+ XFree(info);
+ } else
+ #endif
+@@ -672,9 +689,16 @@ setup(void)
+ if (!XGetWindowAttributes(dpy, parentwin, &wa))
+ die("could not get embedding window attributes: 0x%lx",
+ parentwin);
+- x = 0;
+- y = topbar ? 0 : wa.height - mh;
+- mw = wa.width;
++
++ if (centered) {
++ mw = MIN(MAX(max_textw() + promptw, min_width), wa.width);
++ x = (wa.width - mw) / 2;
++ y = (wa.height - mh) / 2;
++ } else {
++ 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 */
+@@ -733,6 +757,8 @@ main(int argc, char *argv[])
+ topbar = 0;
+ else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
+ fast = 1;
++ else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */
++ centered = 1;
+ else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
+ fstrncmp = strncasecmp;
+ fstrstr = cistrstr;
diff --git a/dmenu/patches/dmenu-highlight-20201211-fcdc159.diff b/dmenu/patches/dmenu-highlight-20201211-fcdc159.diff
new file mode 100644
index 0000000..c3ac5c1
--- /dev/null
+++ b/dmenu/patches/dmenu-highlight-20201211-fcdc159.diff
@@ -0,0 +1,97 @@
+From fcdc1593ed418166f20b7e691a49b1e6eefc116e Mon Sep 17 00:00:00 2001
+From: Nathaniel Evan <nathanielevan@zohomail.com>
+Date: Fri, 11 Dec 2020 11:08:12 +0700
+Subject: [PATCH] Highlight matched text in a different color scheme
+
+---
+ config.def.h | 3 +++
+ dmenu.c | 44 +++++++++++++++++++++++++++++++++++++++++---
+ 2 files changed, 44 insertions(+), 3 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 1edb647..79be73a 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -11,7 +11,10 @@ static const char *colors[SchemeLast][2] = {
+ /* fg bg */
+ [SchemeNorm] = { "#bbbbbb", "#222222" },
+ [SchemeSel] = { "#eeeeee", "#005577" },
++ [SchemeSelHighlight] = { "#ffc978", "#005577" },
++ [SchemeNormHighlight] = { "#ffc978", "#222222" },
+ [SchemeOut] = { "#000000", "#00ffff" },
++ [SchemeOutHighlight] = { "#ffc978", "#00ffff" },
+ };
+ /* -l option; if nonzero, dmenu uses vertical list with given number of lines */
+ static unsigned int lines = 0;
+diff --git a/dmenu.c b/dmenu.c
+index 65f25ce..cce1ad1 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -26,8 +26,7 @@
+ #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
+ /* enums */
+-enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
+-
++enum { SchemeNorm, SchemeSel, SchemeOut, SchemeNormHighlight, SchemeSelHighlight, SchemeOutHighlight, SchemeLast }; /* color schemes */
+ struct item {
+ char *text;
+ struct item *left, *right;
+@@ -113,6 +112,43 @@ cistrstr(const char *s, const char *sub)
+ return NULL;
+ }
+
++static void
++drawhighlights(struct item *item, int x, int y, int maxw)
++{
++ char restorechar, tokens[sizeof text], *highlight, *token;
++ int indentx, highlightlen;
++
++ drw_setscheme(drw, scheme[item == sel ? SchemeSelHighlight : item->out ? SchemeOutHighlight : SchemeNormHighlight]);
++ strcpy(tokens, text);
++ for (token = strtok(tokens, " "); token; token = strtok(NULL, " ")) {
++ highlight = fstrstr(item->text, token);
++ while (highlight) {
++ // Move item str end, calc width for highlight indent, & restore
++ highlightlen = highlight - item->text;
++ restorechar = *highlight;
++ item->text[highlightlen] = '\0';
++ indentx = TEXTW(item->text);
++ item->text[highlightlen] = restorechar;
++
++ // Move highlight str end, draw highlight, & restore
++ restorechar = highlight[strlen(token)];
++ highlight[strlen(token)] = '\0';
++ if (indentx - (lrpad / 2) - 1 < maxw)
++ drw_text(
++ drw,
++ x + indentx - (lrpad / 2) - 1,
++ y,
++ MIN(maxw - indentx, TEXTW(highlight) - lrpad),
++ bh, 0, highlight, 0
++ );
++ highlight[strlen(token)] = restorechar;
++
++ if (strlen(highlight) - strlen(token) < strlen(token)) break;
++ highlight = fstrstr(highlight + strlen(token), token);
++ }
++ }
++}
++
+ static int
+ drawitem(struct item *item, int x, int y, int w)
+ {
+@@ -123,7 +159,9 @@ drawitem(struct item *item, int x, int y, int w)
+ else
+ drw_setscheme(drw, scheme[SchemeNorm]);
+
+- return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
++ int r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
++ drawhighlights(item, x, y, w);
++ return r;
+ }
+
+ static void
+--
+2.29.2
+
diff --git a/dmenu/patches/dmenu-highpriority-20230221-ratakor.diff b/dmenu/patches/dmenu-highpriority-20230221-ratakor.diff
new file mode 100644
index 0000000..2beff03
--- /dev/null
+++ b/dmenu/patches/dmenu-highpriority-20230221-ratakor.diff
@@ -0,0 +1,152 @@
+From 572cffbc38664ee4ad845ff946a04e6a1e0dd4cc Mon Sep 17 00:00:00 2001
+From: Ratakor <contact@ratakor.com>
+Date: Tue, 21 Feb 2023 16:33:24 +0100
+Subject: [PATCH] highpriority: remove color changes and update for 5.2
+
+---
+ dmenu.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 59 insertions(+), 5 deletions(-)
+
+diff --git a/dmenu.c b/dmenu.c
+index 27b7a30..5546757 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -31,9 +31,11 @@ enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
+ struct item {
+ char *text;
+ struct item *left, *right;
+- int out;
++ int out, hp;
+ };
+
++static char **hpitems = NULL;
++static int hplength = 0;
+ static char text[BUFSIZ] = "";
+ static char *embed;
+ static int bh, mw, mh;
+@@ -58,6 +60,42 @@ static Clr *scheme[SchemeLast];
+ static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+ static char *(*fstrstr)(const char *, const char *) = strstr;
+
++static char **
++tokenize(char *source, const char *delim, int *llen)
++{
++ int listlength = 0, list_size = 0;
++ char **list = NULL, *token;
++
++ token = strtok(source, delim);
++ while (token) {
++ if (listlength + 1 >= list_size) {
++ if (!(list = realloc(list, (list_size += 8) * sizeof(*list))))
++ die("Unable to realloc %zu bytes\n", list_size * sizeof(*list));
++ }
++ if (!(list[listlength] = strdup(token)))
++ die("Unable to strdup %zu bytes\n", strlen(token) + 1);
++ token = strtok(NULL, delim);
++ listlength++;
++ }
++
++ *llen = listlength;
++ return list;
++}
++
++static int
++arrayhas(char **list, int length, char *item)
++{
++ int i;
++
++ for (i = 0; i < length; i++) {
++ size_t len1 = strlen(list[i]);
++ size_t len2 = strlen(item);
++ if (!fstrncmp(list[i], item, len1 > len2 ? len2 : len1))
++ return 1;
++ }
++ return 0;
++}
++
+ static unsigned int
+ textw_clamp(const char *str, unsigned int n)
+ {
+@@ -104,6 +142,9 @@ cleanup(void)
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ for (i = 0; i < SchemeLast; i++)
+ free(scheme[i]);
++ for (i = 0; i < hplength; ++i)
++ free(hpitems[i]);
++ free(hpitems);
+ for (i = 0; items && items[i].text; ++i)
+ free(items[i].text);
+ free(items);
+@@ -236,7 +277,7 @@ match(void)
+ char buf[sizeof text], *s;
+ int i, tokc = 0;
+ size_t len, textsize;
+- struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
++ struct item *item, *lhpprefix, *lprefix, *lsubstr, *hpprefixend, *prefixend, *substrend;
+
+ strcpy(buf, text);
+ /* separate input text into tokens to be matched individually */
+@@ -245,7 +286,7 @@ match(void)
+ die("cannot realloc %zu bytes:", tokn * sizeof *tokv);
+ len = tokc ? strlen(tokv[0]) : 0;
+
+- matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
++ matches = lhpprefix = lprefix = lsubstr = matchend = hpprefixend = prefixend = substrend = NULL;
+ textsize = strlen(text) + 1;
+ for (item = items; item && item->text; item++) {
+ for (i = 0; i < tokc; i++)
+@@ -253,14 +294,24 @@ match(void)
+ break;
+ if (i != tokc) /* not all tokens match */
+ continue;
+- /* exact matches go first, then prefixes, then substrings */
++ /* exact matches go first, then prefixes with high priority, then prefixes, then substrings */
+ if (!tokc || !fstrncmp(text, item->text, textsize))
+ appenditem(item, &matches, &matchend);
++ else if (item->hp && !fstrncmp(tokv[0], item->text, len))
++ appenditem(item, &lhpprefix, &hpprefixend);
+ else if (!fstrncmp(tokv[0], item->text, len))
+ appenditem(item, &lprefix, &prefixend);
+ else
+ appenditem(item, &lsubstr, &substrend);
+ }
++ if (lhpprefix) {
++ if (matches) {
++ matchend->right = lhpprefix;
++ lhpprefix->left = matchend;
++ } else
++ matches = lhpprefix;
++ matchend = hpprefixend;
++ }
+ if (lprefix) {
+ if (matches) {
+ matchend->right = lprefix;
+@@ -564,6 +615,7 @@ readstdin(void)
+ line[len - 1] = '\0';
+ items[i].text = line;
+ items[i].out = 0;
++ items[i].hp = arrayhas(hpitems, hplength, items[i].text);
+ line = NULL; /* next call of getline() allocates a new line */
+ }
+ free(line);
+@@ -714,7 +766,7 @@ setup(void)
+ static void
+ usage(void)
+ {
+- die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
++ die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor] [-hp items]\n"
+ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
+ }
+
+@@ -757,6 +809,8 @@ main(int argc, char *argv[])
+ colors[SchemeSel][ColFg] = argv[++i];
+ else if (!strcmp(argv[i], "-w")) /* embedding window id */
+ embed = argv[++i];
++ else if (!strcmp(argv[i], "-hp"))
++ hpitems = tokenize(argv[++i], ",", &hplength);
+ else
+ usage();
+
+--
+2.39.2
+
diff --git a/dmenu/patches/dmenu-mousesupport-5.2.diff b/dmenu/patches/dmenu-mousesupport-5.2.diff
new file mode 100644
index 0000000..eaacea4
--- /dev/null
+++ b/dmenu/patches/dmenu-mousesupport-5.2.diff
@@ -0,0 +1,144 @@
+diff --git a/dmenu.c b/dmenu.c
+index 7cf253b..d276a94 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -528,6 +528,119 @@ draw:
+ drawmenu();
+ }
+
++static void
++buttonpress(XEvent *e)
++{
++ struct item *item;
++ XButtonPressedEvent *ev = &e->xbutton;
++ int x = 0, y = 0, h = bh, w;
++
++ if (ev->window != win)
++ return;
++
++ /* right-click: exit */
++ if (ev->button == Button3)
++ exit(1);
++
++ if (prompt && *prompt)
++ x += promptw;
++
++ /* input field */
++ w = (lines > 0 || !matches) ? mw - x : inputw;
++
++ /* left-click on input: clear input,
++ * NOTE: if there is no left-arrow the space for < is reserved so
++ * add that to the input width */
++ if (ev->button == Button1 &&
++ ((lines <= 0 && ev->x >= 0 && ev->x <= x + w +
++ ((!prev || !curr->left) ? TEXTW("<") : 0)) ||
++ (lines > 0 && ev->y >= y && ev->y <= y + h))) {
++ insert(NULL, -cursor);
++ drawmenu();
++ return;
++ }
++ /* middle-mouse click: paste selection */
++ if (ev->button == Button2) {
++ XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
++ utf8, utf8, win, CurrentTime);
++ drawmenu();
++ return;
++ }
++ /* scroll up */
++ if (ev->button == Button4 && prev) {
++ sel = curr = prev;
++ calcoffsets();
++ drawmenu();
++ return;
++ }
++ /* scroll down */
++ if (ev->button == Button5 && next) {
++ sel = curr = next;
++ calcoffsets();
++ drawmenu();
++ return;
++ }
++ if (ev->button != Button1)
++ return;
++ if (ev->state & ~ControlMask)
++ return;
++ if (lines > 0) {
++ /* vertical list: (ctrl)left-click on item */
++ w = mw - x;
++ for (item = curr; item != next; item = item->right) {
++ y += h;
++ if (ev->y >= y && ev->y <= (y + h)) {
++ puts(item->text);
++ if (!(ev->state & ControlMask))
++ exit(0);
++ sel = item;
++ if (sel) {
++ sel->out = 1;
++ drawmenu();
++ }
++ return;
++ }
++ }
++ } else if (matches) {
++ /* left-click on left arrow */
++ x += inputw;
++ w = TEXTW("<");
++ if (prev && curr->left) {
++ if (ev->x >= x && ev->x <= x + w) {
++ sel = curr = prev;
++ calcoffsets();
++ drawmenu();
++ return;
++ }
++ }
++ /* horizontal list: (ctrl)left-click on item */
++ for (item = curr; item != next; item = item->right) {
++ x += w;
++ w = MIN(TEXTW(item->text), mw - x - TEXTW(">"));
++ if (ev->x >= x && ev->x <= x + w) {
++ puts(item->text);
++ if (!(ev->state & ControlMask))
++ exit(0);
++ sel = item;
++ if (sel) {
++ sel->out = 1;
++ drawmenu();
++ }
++ return;
++ }
++ }
++ /* left-click on right arrow */
++ w = TEXTW(">");
++ x = mw - w;
++ if (next && ev->x >= x && ev->x <= x + w) {
++ sel = curr = next;
++ calcoffsets();
++ drawmenu();
++ return;
++ }
++ }
++}
++
+ static void
+ paste(void)
+ {
+@@ -582,6 +695,9 @@ run(void)
+ break;
+ cleanup();
+ exit(1);
++ case ButtonPress:
++ buttonpress(&ev);
++ break;
+ case Expose:
+ if (ev.xexpose.count == 0)
+ drw_map(drw, win, 0, 0, mw, mh);
+@@ -679,7 +795,8 @@ setup(void)
+ /* create menu window */
+ swa.override_redirect = True;
+ swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
+- swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
++ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask |
++ ButtonPressMask;
+ win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
+ CopyFromParent, CopyFromParent, CopyFromParent,
+ CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
diff --git a/dmenu/patches/dmenu-xresources-alt-5.0.diff b/dmenu/patches/dmenu-xresources-alt-5.0.diff
new file mode 100644
index 0000000..f7f052f
--- /dev/null
+++ b/dmenu/patches/dmenu-xresources-alt-5.0.diff
@@ -0,0 +1,182 @@
+diff -rupN orig/config.def.h patched/config.def.h
+--- orig/config.def.h 2021-04-16 06:30:47.713924755 +0300
++++ patched/config.def.h 2021-04-16 06:34:14.956933252 +0300
+@@ -2,16 +2,25 @@
+ /* 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 char font[] = "monospace:size=10";
+ static const char *fonts[] = {
+- "monospace:size=10"
++ font,
++ "monospace:size=10",
+ };
+-static const char *prompt = NULL; /* -p option; prompt to the left of input field */
+-static const char *colors[SchemeLast][2] = {
++
++static char *prompt = NULL; /* -p option; prompt to the left of input field */
++
++static char normfgcolor[] = "#bbbbbb";
++static char normbgcolor[] = "#222222";
++static char selfgcolor[] = "#eeeeee";
++static char selbgcolor[] = "#005577";
++static char *colors[SchemeLast][2] = {
+ /* fg bg */
+- [SchemeNorm] = { "#bbbbbb", "#222222" },
+- [SchemeSel] = { "#eeeeee", "#005577" },
+- [SchemeOut] = { "#000000", "#00ffff" },
++ [SchemeNorm] = { normfgcolor, normbgcolor },
++ [SchemeSel] = { selfgcolor, selbgcolor },
++ [SchemeOut] = { "#000000", "#00ffff" },
+ };
+ /* -l option; if nonzero, dmenu uses vertical list with given number of lines */
+ static unsigned int lines = 0;
+@@ -21,3 +30,15 @@ static unsigned int lines = 0;
+ * for example: " /?\"&[]"
+ */
+ static const char worddelimiters[] = " ";
++
++/*
++ * Xresources preferences to load at startup
++ */
++ResourcePref resources[] = {
++ { "font", STRING, &font },
++ { "normfgcolor", STRING, &normfgcolor },
++ { "normbgcolor", STRING, &normbgcolor },
++ { "selfgcolor", STRING, &selfgcolor },
++ { "selbgcolor", STRING, &selbgcolor },
++ { "prompt", STRING, &prompt },
++};
+diff -rupN orig/dmenu.c patched/dmenu.c
+--- orig/dmenu.c 2021-04-16 06:30:47.715924755 +0300
++++ patched/dmenu.c 2021-04-16 06:30:59.668925245 +0300
+@@ -11,6 +11,7 @@
+ #include <X11/Xlib.h>
+ #include <X11/Xatom.h>
+ #include <X11/Xutil.h>
++#include <X11/Xresource.h>
+ #ifdef XINERAMA
+ #include <X11/extensions/Xinerama.h>
+ #endif
+@@ -53,6 +54,21 @@ static XIC xic;
+ static Drw *drw;
+ static Clr *scheme[SchemeLast];
+
++/* Xresources preferences */
++enum resource_type {
++ STRING = 0,
++ INTEGER = 1,
++ FLOAT = 2
++};
++typedef struct {
++ char *name;
++ enum resource_type type;
++ void *dst;
++} ResourcePref;
++
++static void load_xresources(void);
++static void resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst);
++
+ #include "config.h"
+
+ static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+@@ -395,7 +411,7 @@ keypress(XKeyEvent *ev)
+
+ switch(ksym) {
+ default:
+-insert:
++ insert:
+ if (!iscntrl(*buf))
+ insert(buf, len);
+ break;
+@@ -547,6 +563,54 @@ readstdin(void)
+ lines = MIN(lines, i);
+ }
+
++void
++resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
++{
++ char *sdst = NULL;
++ int *idst = NULL;
++ float *fdst = NULL;
++ sdst = dst;
++ idst = dst;
++ fdst = dst;
++ char fullname[256];
++ char *type;
++ XrmValue ret;
++ snprintf(fullname, sizeof(fullname), "%s.%s", "dmenu", name);
++ fullname[sizeof(fullname) - 1] = '\0';
++ XrmGetResource(db, fullname, "*", &type, &ret);
++ if (!(ret.addr == NULL || strncmp("String", type, 64)))
++ {
++ switch (rtype) {
++ case STRING:
++ strcpy(sdst, ret.addr);
++ break;
++ case INTEGER:
++ *idst = strtoul(ret.addr, NULL, 10);
++ break;
++ case FLOAT:
++ *fdst = strtof(ret.addr, NULL);
++ break;
++ }
++ }
++}
++
++void
++load_xresources(void)
++{
++ Display *display;
++ char *resm;
++ XrmDatabase db;
++ ResourcePref *p;
++ display = XOpenDisplay(NULL);
++ resm = XResourceManagerString(display);
++ if (!resm)
++ return;
++ db = XrmGetStringDatabase(resm);
++ for (p = resources; p < resources + LENGTH(resources); p++)
++ resource_load(db, p->name, p->type, p->dst);
++ XCloseDisplay(display);
++}
++
+ static void
+ run(void)
+ {
+@@ -700,6 +764,9 @@ main(int argc, char *argv[])
+ XWindowAttributes wa;
+ int i, fast = 0;
+
++ XrmInitialize();
++ load_xresources();
++
+ for (i = 1; i < argc; i++)
+ /* these options take no arguments */
+ if (!strcmp(argv[i], "-v")) { /* prints version information */
+diff -rupN orig/drw.c patched/drw.c
+--- orig/drw.c 2021-04-16 06:30:47.718924755 +0300
++++ patched/drw.c 2021-04-16 06:30:59.670925245 +0300
+@@ -208,7 +208,7 @@ drw_clr_create(Drw *drw, Clr *dest, cons
+ /* 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)
++drw_scm_create(Drw *drw, char *clrnames[], size_t clrcount)
+ {
+ size_t i;
+ Clr *ret;
+diff -rupN orig/drw.h patched/drw.h
+--- orig/drw.h 2021-04-16 06:30:47.718924755 +0300
++++ patched/drw.h 2021-04-16 06:30:59.671925245 +0300
+@@ -39,7 +39,7 @@ void drw_font_getexts(Fnt *font, const c
+
+ /* 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);
++Clr *drw_scm_create(Drw *drw, char *clrnames[], size_t clrcount);
+
+ /* Cursor abstraction */
+ Cur *drw_cur_create(Drw *drw, int shape);
diff --git a/dmenu/stest.1 b/dmenu/stest.1
new file mode 100644
index 0000000..2667d8a
--- /dev/null
+++ b/dmenu/stest.1
@@ -0,0 +1,90 @@
+.TH STEST 1 dmenu\-VERSION
+.SH NAME
+stest \- filter a list of files by properties
+.SH SYNOPSIS
+.B stest
+.RB [ -abcdefghlpqrsuwx ]
+.RB [ -n
+.IR file ]
+.RB [ -o
+.IR file ]
+.RI [ file ...]
+.SH DESCRIPTION
+.B stest
+takes a list of files and filters by the files' properties, analogous to
+.IR test (1).
+Files which pass all tests are printed to stdout. If no files are given, stest
+reads files from stdin.
+.SH OPTIONS
+.TP
+.B \-a
+Test hidden files.
+.TP
+.B \-b
+Test that files are block specials.
+.TP
+.B \-c
+Test that files are character specials.
+.TP
+.B \-d
+Test that files are directories.
+.TP
+.B \-e
+Test that files exist.
+.TP
+.B \-f
+Test that files are regular files.
+.TP
+.B \-g
+Test that files have their set-group-ID flag set.
+.TP
+.B \-h
+Test that files are symbolic links.
+.TP
+.B \-l
+Test the contents of a directory given as an argument.
+.TP
+.BI \-n " file"
+Test that files are newer than
+.IR file .
+.TP
+.BI \-o " file"
+Test that files are older than
+.IR file .
+.TP
+.B \-p
+Test that files are named pipes.
+.TP
+.B \-q
+No files are printed, only the exit status is returned.
+.TP
+.B \-r
+Test that files are readable.
+.TP
+.B \-s
+Test that files are not empty.
+.TP
+.B \-u
+Test that files have their set-user-ID flag set.
+.TP
+.B \-v
+Invert the sense of tests, only failing files pass.
+.TP
+.B \-w
+Test that files are writable.
+.TP
+.B \-x
+Test that files are executable.
+.SH EXIT STATUS
+.TP
+.B 0
+At least one file passed all tests.
+.TP
+.B 1
+No files passed all tests.
+.TP
+.B 2
+An error occurred.
+.SH SEE ALSO
+.IR dmenu (1),
+.IR test (1)
diff --git a/dmenu/stest.c b/dmenu/stest.c
new file mode 100644
index 0000000..e27d3a5
--- /dev/null
+++ b/dmenu/stest.c
@@ -0,0 +1,109 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arg.h"
+char *argv0;
+
+#define FLAG(x) (flag[(x)-'a'])
+
+static void test(const char *, const char *);
+static void usage(void);
+
+static int match = 0;
+static int flag[26];
+static struct stat old, new;
+
+static void
+test(const char *path, const char *name)
+{
+ struct stat st, ln;
+
+ if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */
+ && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */
+ && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */
+ && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */
+ && (!FLAG('e') || access(path, F_OK) == 0) /* exists */
+ && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */
+ && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */
+ && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */
+ && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */
+ && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */
+ && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */
+ && (!FLAG('r') || access(path, R_OK) == 0) /* readable */
+ && (!FLAG('s') || st.st_size > 0) /* not empty */
+ && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */
+ && (!FLAG('w') || access(path, W_OK) == 0) /* writable */
+ && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */
+ if (FLAG('q'))
+ exit(0);
+ match = 1;
+ puts(name);
+ }
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] "
+ "[-n file] [-o file] [file...]\n", argv0);
+ exit(2); /* like test(1) return > 1 on error */
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct dirent *d;
+ char path[PATH_MAX], *line = NULL, *file;
+ size_t linesiz = 0;
+ ssize_t n;
+ DIR *dir;
+ int r;
+
+ ARGBEGIN {
+ case 'n': /* newer than file */
+ case 'o': /* older than file */
+ file = EARGF(usage());
+ if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old))))
+ perror(file);
+ break;
+ default:
+ /* miscellaneous operators */
+ if (strchr("abcdefghlpqrsuvwx", ARGC()))
+ FLAG(ARGC()) = 1;
+ else
+ usage(); /* unknown flag */
+ } ARGEND;
+
+ if (!argc) {
+ /* read list from stdin */
+ while ((n = getline(&line, &linesiz, stdin)) > 0) {
+ if (line[n - 1] == '\n')
+ line[n - 1] = '\0';
+ test(line, line);
+ }
+ free(line);
+ } else {
+ for (; argc; argc--, argv++) {
+ if (FLAG('l') && (dir = opendir(*argv))) {
+ /* test directory contents */
+ while ((d = readdir(dir))) {
+ r = snprintf(path, sizeof path, "%s/%s",
+ *argv, d->d_name);
+ if (r >= 0 && (size_t)r < sizeof path)
+ test(path, d->d_name);
+ }
+ closedir(dir);
+ } else {
+ test(*argv, *argv);
+ }
+ }
+ }
+ return match ? 0 : 1;
+}
diff --git a/dmenu/util.c b/dmenu/util.c
new file mode 100644
index 0000000..96b82c9
--- /dev/null
+++ b/dmenu/util.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include <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/dmenu/util.h b/dmenu/util.h
new file mode 100644
index 0000000..f633b51
--- /dev/null
+++ b/dmenu/util.h
@@ -0,0 +1,8 @@
+/* See LICENSE file for copyright and license details. */
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+
+void die(const char *fmt, ...);
+void *ecalloc(size_t nmemb, size_t size);
diff --git a/dwm/.gitignore b/dwm/.gitignore
new file mode 100644
index 0000000..f913c1b
--- /dev/null
+++ b/dwm/.gitignore
@@ -0,0 +1,3 @@
+*.o
+dwm
+config.h
diff --git a/dwm/LICENSE b/dwm/LICENSE
new file mode 100644
index 0000000..3e1c4a2
--- /dev/null
+++ b/dwm/LICENSE
@@ -0,0 +1,39 @@
+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>
+© 2022-2023 Ratakor <ratakor@disroot.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/dwm/Makefile b/dwm/Makefile
new file mode 100644
index 0000000..77bcbc0
--- /dev/null
+++ b/dwm/Makefile
@@ -0,0 +1,51 @@
+# dwm - dynamic window manager
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = drw.c dwm.c util.c
+OBJ = ${SRC:.c=.o}
+
+all: options dwm
+
+options:
+ @echo dwm build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+.c.o:
+ ${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.h config.mk
+
+config.h:
+ cp config.def.h $@
+
+dwm: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ rm -f dwm ${OBJ} dwm-${VERSION}.tar.gz
+
+dist: clean
+ mkdir -p dwm-${VERSION}
+ cp -R LICENSE Makefile README config.def.h config.mk\
+ dwm.1 drw.h util.h ${SRC} dwm.png transient.c dwm-${VERSION}
+ tar -cf dwm-${VERSION}.tar dwm-${VERSION}
+ gzip dwm-${VERSION}.tar
+ rm -rf dwm-${VERSION}
+
+install: all
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f dwm ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/dwm
+ mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1
+ chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1
+
+uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/dwm\
+ ${DESTDIR}${MANPREFIX}/man1/dwm.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/dwm/README.md b/dwm/README.md
new file mode 100644
index 0000000..6b07c86
--- /dev/null
+++ b/dwm/README.md
@@ -0,0 +1,88 @@
+# Ratakor's build of dwm
+
+## Important notes
+For default keybinds read the man pages, it is up to date, there are no default
+keybinds for st or dmenu to allow the use of other terminal or application
+launcher without recompiling.
+
+Thus this build of dwm can be used as a
+[package](https://github.com/ratakor/ratakor-repo) and modified only through
+[sxhkd](https://github.com/baskerville/sxhkd) and
+[Xresources](https://wiki.archlinux.org/title/X_resources).
+
+Gaps are disabled by default, it's possible to enable them via Xresources by
+setting dwm.enablegaps to +1 or by setting enablegaps to 1 in config.h, it's
+even possible to enable them on the fly with super + shift + g.
+
+## patch added and changes iirc
+- attach aside
+- rainbow tab
+- Xresources but default to dracula theme
+- alpha (transparency)
+- quitprompt (super + shift + e)
+- bartab (all app are shown in status bar and have an optional border)
+- 3 layouts, tile, bottom stack and floating
+- true fullscreen (super + f)
+- colors in status bar
+- status bar on all monitors
+- cycle layouts (super + shift + space)
+- smartborders
+- gaps (also in bar, everything can be toggled on/off with super + shift + g)
+- restart with `kill -10 $(pidof dwm)`
+
+## TODO
+
+- Add rofi or any application launcher support to quitprompt
+- Fix ugly hard code of font because icons are too big or too small
+- Add an azerty configuration switch maybe or autodetect layout idk
+
+---
+
+dwm - dynamic window manager
+============================
+dwm is an extremely fast, small, and dynamic window manager for X.
+
+
+Requirements
+------------
+In order to build dwm you need the Xlib header files.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (dwm is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install dwm (if
+necessary as root):
+
+ make clean install
+
+
+Running dwm
+-----------
+Add the following line to your .xinitrc to start dwm using startx:
+
+ exec dwm
+
+In order to connect dwm to a specific display, make sure that
+the DISPLAY environment variable is set correctly, e.g.:
+
+ DISPLAY=foo.bar:1 exec dwm
+
+(This will start dwm on display :1 of the host foo.bar.)
+
+In order to display status info in the bar, you can do something
+like this in your .xinitrc:
+
+ while xsetroot -name "`date` `uptime | sed 's/.*,//'`"
+ do
+ sleep 1
+ done &
+ exec dwm
+
+
+Configuration
+-------------
+The configuration of dwm is done by creating a custom config.h
+and (re)compiling the source code.
diff --git a/dwm/config.def.h b/dwm/config.def.h
new file mode 100644
index 0000000..9d555ea
--- /dev/null
+++ b/dwm/config.def.h
@@ -0,0 +1,183 @@
+/* See LICENSE file for copyright and license details. */
+
+/* appearance */
+static unsigned int borderpx = 2; /* border pixel of windows */
+static unsigned int snap = 5; /* snap pixel */
+static unsigned int gaps = 15; /* gaps pixel */
+static int enablegaps = 0; /* 0 means no gaps */
+static int showbar = 1; /* 0 means no bar */
+static int topbar = 1; /* 0 means bottom bar */
+static int barborders = 1; /* 0 means no borders in bar */
+static const char *fonts[] = {
+ "Agave:size=10", /* this is for nerd font icons with agave nerd font */
+ "monospace:size=13"
+};
+static char normbgcolor[] = "#282a36";
+static char normbordercolor[] = "#44475a";
+static char normfgcolor[] = "#f8f8f2"; /* 1 */
+static char selbgcolor[] = "#282a36";
+static char selbordercolor[] = "#bd93f9";
+static char selfgcolor[] = "#bd93f9"; /* 2 */
+static char blue[] = "#bd93f9"; /* 3 (purple in dracula theme) */
+static char green[] = "#50fa7b"; /* 4 */
+static char orange[] = "#ffb86c"; /* 5 */
+static char red[] = "#ff5555"; /* 6 */
+static char yellow[] = "#f1fa8c"; /* 7 */
+static char magenta[] = "#ff79c6"; /* 8 (pink in dracula theme) */
+static char cyan[] = "#8be9fd"; /* 9 */
+static char last[] = "#6272a4";
+static const unsigned int alpha = 0xd9;
+static char *colors[][3] = {
+ /* fg bg border */
+ [SchemeNorm] = { normfgcolor, normbgcolor, normbordercolor },
+ [SchemeSel] = { selfgcolor, selbgcolor, selbordercolor },
+ [SchemeBlue] = { blue, normbgcolor, normbordercolor },
+ [SchemeGreen] = { green, normbgcolor, normbordercolor },
+ [SchemeOrange] = { orange, normbgcolor, normbordercolor },
+ [SchemeRed] = { red, normbgcolor, normbordercolor },
+ [SchemeYellow] = { yellow, normbgcolor, normbordercolor },
+ [SchemeMagenta] = { magenta, normbgcolor, normbordercolor },
+ [SchemeCyan] = { cyan, normbgcolor, normbordercolor },
+};
+static const unsigned int alphas[][3] = {
+ /* fg bg border */
+ [SchemeNorm] = { OPAQUE, alpha, OPAQUE },
+ [SchemeSel] = { OPAQUE, alpha, OPAQUE },
+ [SchemeBlue] = { OPAQUE, alpha, OPAQUE },
+ [SchemeGreen] = { OPAQUE, alpha, OPAQUE },
+ [SchemeOrange] = { OPAQUE, alpha, OPAQUE },
+ [SchemeRed] = { OPAQUE, alpha, OPAQUE },
+ [SchemeYellow] = { OPAQUE, alpha, OPAQUE },
+ [SchemeMagenta] = { OPAQUE, alpha, OPAQUE },
+ [SchemeCyan] = { OPAQUE, alpha, OPAQUE },
+};
+
+static const unsigned int tagalpha[] = { OPAQUE, alpha };
+
+/* tagging */
+static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+
+static char *tagsel[][2] = {
+ { normfgcolor, blue },
+ { normfgcolor, magenta },
+ { normfgcolor, red },
+ { normfgcolor, orange },
+ { normbgcolor, yellow },
+ { normbgcolor, green },
+ { normbgcolor, cyan },
+ { normbgcolor, normfgcolor },
+ { normfgcolor, last },
+};
+
+/* Xresources preferences to load at startup */
+ResourcePref resources[] = {
+ { "borderpx", INTEGER, &borderpx },
+ { "snap", INTEGER, &snap },
+ { "gaps", INTEGER, &gaps },
+ { "enablegaps", INTEGER, &enablegaps }, /* bool */
+ { "showbar", INTEGER, &showbar }, /* bool */
+ { "topbar", INTEGER, &topbar }, /* bool */
+ { "barborders", INTEGER, &barborders }, /* bool */
+ { "normbgcolor", STRING, &normbgcolor },
+ { "normbordercolor", STRING, &normbordercolor },
+ { "normfgcolor", STRING, &normfgcolor },
+ { "selbgcolor", STRING, &selbgcolor },
+ { "selbordercolor", STRING, &selbordercolor },
+ { "selfgcolor", STRING, &selfgcolor },
+ { "color4", STRING, &blue },
+ { "color2", STRING, &green },
+ { "orange", STRING, &orange },
+ { "color1", STRING, &red },
+ { "color3", STRING, &yellow },
+ { "color5", STRING, &magenta },
+ { "color6", STRING, &cyan },
+ { "last", STRING, &last }
+};
+
+static const Rule rules[] = {
+ /* xprop(1):
+ * WM_CLASS(STRING) = instance, class
+ * WM_NAME(STRING) = title
+ */
+ /* class instance title tags mask isfloating monitor */
+ { "dummy", NULL, NULL, 1 << 8, 0, -1 },
+};
+
+/* layout(s) */
+static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */
+static const int nmaster = 1; /* number of clients in master area */
+static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */
+static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */
+static const int attachbelow = 1; /* 1 means attach after the currently active window */
+
+static const Layout layouts[] = {
+ /* symbol arrange function */
+ { "[]=", tile }, /* first entry is default */
+ { "TTT", bstack },
+ { "><>", NULL }, /* no layout function means floating behavior */
+ { NULL, NULL }, /* this is for the cyclelayout patch */
+};
+
+/* key definitions */
+#define MODKEY Mod4Mask
+#define TAGKEYS(KEY,TAG) \
+ { MODKEY, KEY, view, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \
+ { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} },
+
+/* helper for spawning shell commands in the pre dwm-5.0 fashion */
+/* #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } */
+
+static const Key keys[] = {
+ /* modifier key function argument */
+ { MODKEY|ShiftMask, XK_b, togglebar, {0} },
+ { MODKEY, XK_f, togglefullscr, {0} },
+ { MODKEY, XK_j, focusstack, {.i = +1 } },
+ { MODKEY, XK_k, focusstack, {.i = -1 } },
+ { MODKEY, XK_i, incnmaster, {.i = +1 } },
+ { MODKEY, XK_o, incnmaster, {.i = -1 } },
+ { MODKEY, XK_h, setmfact, {.f = -0.05} },
+ { MODKEY, XK_l, setmfact, {.f = +0.05} },
+ { MODKEY|ShiftMask, XK_g, togglegaps, {0} },
+ { MODKEY|ShiftMask, XK_Return, zoom, {0} },
+ { MODKEY|ShiftMask, XK_j, zoom, {0} },
+ { MODKEY|ShiftMask, XK_k, zoom, {0} },
+ { MODKEY, XK_Tab, view, {0} },
+ { MODKEY|ShiftMask, XK_q, killclient, {0} },
+ { MODKEY, XK_space, togglefloating, {0} },
+ { MODKEY|ShiftMask, XK_space, cyclelayout, {.i = +1 } },
+ { 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_e, quitprompt, {0} },
+};
+
+/* 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, cyclelayout, {.i = +1 } },
+ /* { ClkWinTitle, 0, Button2, zoom, {0} }, */
+ /* { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, */
+ { ClkClientWin, MODKEY, Button1, movemouse, {0} },
+ { ClkClientWin, MODKEY|ShiftMask, Button1, resizemouse, {0} },
+ { ClkClientWin, MODKEY, Button2, togglefloating, {0} },
+ { ClkClientWin, MODKEY, Button3, resizemouse, {0} },
+ { ClkTagBar, 0, Button1, view, {0} },
+ { ClkTagBar, 0, Button3, toggleview, {0} },
+ { ClkTagBar, MODKEY, Button1, tag, {0} },
+ { ClkTagBar, MODKEY, Button3, toggletag, {0} },
+};
diff --git a/dwm/config.mk b/dwm/config.mk
new file mode 100644
index 0000000..041c1cf
--- /dev/null
+++ b/dwm/config.mk
@@ -0,0 +1,38 @@
+# 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} -lXrender
+
+# flags
+CPPFLAGS += -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
+CFLAGS += -std=c99 -pedantic -Wall -Wno-deprecated-declarations -O3 ${INCS} ${CPPFLAGS}
+LDFLAGS += ${LIBS}
+
+# Solaris
+#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\"
+#LDFLAGS = ${LIBS}
+
+# compiler and linker
+CC = cc
diff --git a/dwm/drw.c b/dwm/drw.c
new file mode 100644
index 0000000..f02fb49
--- /dev/null
+++ b/dwm/drw.c
@@ -0,0 +1,452 @@
+/* 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, Visual *visual, unsigned int depth, Colormap cmap)
+{
+ Drw *drw = ecalloc(1, sizeof(Drw));
+
+ drw->dpy = dpy;
+ drw->screen = screen;
+ drw->root = root;
+ drw->w = w;
+ drw->h = h;
+ drw->visual = visual;
+ drw->depth = depth;
+ drw->cmap = cmap;
+ drw->drawable = XCreatePixmap(dpy, root, w, h, depth);
+ drw->gc = XCreateGC(dpy, drw->drawable, 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, drw->depth);
+}
+
+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, unsigned int alpha)
+{
+ if (!drw || !dest || !clrname)
+ return;
+
+ if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap,
+ clrname, dest))
+ die("error, cannot allocate color '%s'", clrname);
+
+ dest->pixel = (dest->pixel & 0x00ffffffU) | (alpha << 24);
+}
+
+/* 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, char *clrnames[], const unsigned int alphas[], 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], alphas[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, drw->visual, drw->cmap);
+ 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/dwm/drw.h b/dwm/drw.h
new file mode 100644
index 0000000..cd5bba3
--- /dev/null
+++ b/dwm/drw.h
@@ -0,0 +1,61 @@
+/* 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;
+ Visual *visual;
+ unsigned int depth;
+ Colormap cmap;
+ 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, Visual *visual, unsigned int depth, Colormap cmap);
+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, unsigned int alpha);
+Clr *drw_scm_create(Drw *drw, char *clrnames[], const unsigned int alphas[], size_t clrcount);
+
+/* Cursor abstraction */
+Cur *drw_cur_create(Drw *drw, int shape);
+void drw_cur_free(Drw *drw, Cur *cursor);
+
+/* Drawing context manipulation */
+void drw_setfontset(Drw *drw, Fnt *set);
+void drw_setscheme(Drw *drw, Clr *scm);
+
+/* Drawing functions */
+void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
+int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
+
+/* Map functions */
+void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
diff --git a/dwm/dwm.1 b/dwm/dwm.1
new file mode 100644
index 0000000..5b91e54
--- /dev/null
+++ b/dwm/dwm.1
@@ -0,0 +1,177 @@
+.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, bstack
+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 bstack layout master area is at
+the top and the stacking area is at the bottom. 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
+The attach below patch makes newly spawned windows attach after the currently
+selected window
+.P
+dwm draws a small border around windows to indicate the focus state.
+.P
+kill -10 $(pidof dwm) is the command to restart dwm.
+.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 cycle between all layouts.
+.TP
+.B Button3
+click on a tag label adds/removes all windows with that tag to/from the view.
+.TP
+.B Mod4\-Button1
+click on a tag label applies that tag to the focused window.
+.TP
+.B Mod4\-Button3
+click on a tag label adds/removes that tag to/from the focused window.
+.SS Keyboard commands
+.TP
+.B Mod4\-,
+Focus previous screen, if any.
+.TP
+.B Mod4\-.
+Focus next screen, if any.
+.TP
+.B Mod4\-Shift\-,
+Send focused window to previous screen, if any.
+.TP
+.B Mod4\-Shift\-.
+Send focused window to next screen, if any.
+.TP
+.B Mod4\-Shift\-b
+Toggles bar on and off.
+.TP
+.B Mod4\-Shift\-g
+Toggles gaps on and off.
+.TP
+.B Mod4\-Shift\-space
+Cycle between all layouts.
+.TP
+.B Mod4\-j
+Focus next window.
+.TP
+.B Mod4\-k
+Focus previous window.
+.TP
+.B Mod4\-i
+Increase number of windows in master area.
+.TP
+.B Mod4\-o
+Decrease number of windows in master area.
+.TP
+.B Mod4\-l
+Increase master area size.
+.TP
+.B Mod4\-h
+Decrease master area size.
+.TP
+.B Mod4\-Shift\-Return or Mod4\-Shift\-j or Mod4\-Shift\-k
+Zooms/cycles focused window to/from master area (tiled layouts only).
+.TP
+.B Mod4\-Shift\-q
+Close focused window.
+.TP
+.B Mod4\-f
+Toggle fullscreen for focused window.
+.TP
+.B Mod4\-space
+Toggle focused window between tiled and floating state.
+.TP
+.B Mod4\-Tab
+Toggles to the previously selected tags.
+.TP
+.B Mod4\-Shift\-[1..n]
+Apply nth tag to focused window.
+.TP
+.B Mod4\-Shift\-0
+Apply all tags to focused window.
+.TP
+.B Mod4\-Control\-Shift\-[1..n]
+Add/remove nth tag to/from focused window.
+.TP
+.B Mod4\-[1..n]
+View all windows with nth tag.
+.TP
+.B Mod4\-0
+View all windows with any tag.
+.TP
+.B Mod4\-Control\-[1..n]
+Add/remove all windows with nth tag to/from the view.
+.TP
+.B Mod4\-Shift\-e
+Show a prompt with dmenu to either quit or restart dwm.
+.SS Mouse commands
+.TP
+.B Mod4\-Button1
+Move focused window while dragging. Tiled windows will be toggled to the floating state.
+.TP
+.B Mod4\-Button2
+Toggles focused window between floating and tiled state.
+.TP
+.B Mod4\-Shift\-Button1 or Mod4\-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. But this build is intended to be
+run with
+.BR sxhkd (1)
+and
+.BR xrdb (1)
+instead.
+.BR sxhkd (1).
+.SH SEE ALSO
+.BR dmenu (1),
+.BR st (1),
+.BR xrdb (1),
+.BR sxhkd (1)
+.SH ISSUES
+Java applications which use the XToolkit/XAWT backend may draw grey windows
+only. The XToolkit/XAWT backend breaks ICCCM-compliance in recent JDK 1.5 and early
+JDK 1.6 versions, because it assumes a reparenting window manager. Possible workarounds
+are using JDK 1.4 (which doesn't contain the XToolkit/XAWT backend) or setting the
+environment variable
+.BR AWT_TOOLKIT=MToolkit
+(to use the older Motif backend instead) or running
+.B xprop -root -f _NET_WM_NAME 32a -set _NET_WM_NAME LG3D
+or
+.B wmname LG3D
+(to pretend that a non-reparenting window manager is running that the
+XToolkit/XAWT backend can recognize) or when using OpenJDK setting the environment variable
+.BR _JAVA_AWT_WM_NONREPARENTING=1 .
+.SH BUGS
+Send all bug reports with a patch to hackers@suckless.org.
diff --git a/dwm/dwm.c b/dwm/dwm.c
new file mode 100644
index 0000000..56735d3
--- /dev/null
+++ b/dwm/dwm.c
@@ -0,0 +1,2525 @@
+/* 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/Xresource.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)
+
+#define OPAQUE 0xffU
+
+/* enums */
+enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */
+enum { SchemeNorm, SchemeSel, SchemeBlue, SchemeGreen, SchemeOrange,
+ SchemeRed, SchemeYellow, SchemeMagenta, SchemeCyan
+ }; /* 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, 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, vp, sp; /* bar geometry (vertical/side padding) */
+ 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 enablegaps;
+ Client *clients;
+ Client *sel;
+ Client *stack;
+ Monitor *next;
+ Window barwin;
+ const Layout *lt[2];
+};
+
+typedef struct {
+ const char *class;
+ const char *instance;
+ const char *title;
+ unsigned int tags;
+ int isfloating;
+ int monitor;
+} Rule;
+
+/* Xresources preferences */
+enum resource_type {
+ STRING = 0,
+ INTEGER = 1,
+ FLOAT = 2
+};
+
+typedef struct {
+ char *name;
+ enum resource_type type;
+ void *dst;
+} ResourcePref;
+
+/* function declarations */
+static void applyrules(Client *c);
+static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int *bw, int interact);
+static void arrange(Monitor *m);
+static void arrangemon(Monitor *m);
+static void attach(Client *c);
+static void attachBelow(Client *c);
+static void attachstack(Client *c);
+static void bartab(Monitor *m, int offx, int sw);
+static void bstack(Monitor *m);
+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 cyclelayout(const Arg *arg);
+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 load_xresources(void);
+static void manage(Window w, XWindowAttributes *wa);
+static void mappingnotify(XEvent *e);
+static void maprequest(XEvent *e);
+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 void quitprompt(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 bw, int interact);
+static void resizeclient(Client *c, int x, int y, int w, int h, int bw);
+static void resizemouse(const Arg *arg);
+static void resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst);
+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 sigrestart(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 togglefullscr(const Arg *arg);
+static void togglegaps(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 xinitvisual(void);
+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 int restart = 0;
+static Cur *cursor[CurLast];
+static Clr **scheme;
+static Clr **tagscheme;
+static Display *dpy;
+static Drw *drw;
+static Monitor *mons, *selmon;
+static Window root, wmcheckwin;
+
+static int useargb = 0;
+static Visual *visual;
+static int depth;
+static Colormap cmap;
+
+/* configuration, allows nested code to access above variables */
+#include "config.h"
+
+/* compile-time check if all tags fit into an unsigned int bit array. */
+struct NumTags {
+ char limitexceeded[LENGTH(tags) > 31 ? -1 : 1];
+};
+
+/* function implementations */
+void
+applyrules(Client *c)
+{
+ const char *class, *instance;
+ unsigned int i;
+ const Rule *r;
+ Monitor *m;
+ XClassHint ch = { NULL, NULL };
+
+ /* rule matching */
+ c->isfloating = 0;
+ c->tags = 0;
+ XGetClassHint(dpy, c->win, &ch);
+ class = ch.res_class ? ch.res_class : broken;
+ instance = ch.res_name ? ch.res_name : broken;
+
+ for (i = 0; i < LENGTH(rules); i++) {
+ r = &rules[i];
+ if ((!r->title || strstr(c->name, r->title))
+ && (!r->class || strstr(class, r->class))
+ && (!r->instance || strstr(instance, r->instance))) {
+ c->isfloating = r->isfloating;
+ c->tags |= r->tags;
+ for (m = mons; m && m->num != r->monitor; m = m->next);
+ if (m)
+ c->mon = m;
+ }
+ }
+ if (ch.res_class)
+ XFree(ch.res_class);
+ if (ch.res_name)
+ XFree(ch.res_name);
+ c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags];
+}
+
+int
+applysizehints(Client *c, int *x, int *y, int *w, int *h, int *bw, 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 * *bw < 0)
+ *x = 0;
+ if (*y + *h + 2 * *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 * *bw <= m->wx)
+ *x = m->wx;
+ if (*y + *h + 2 * *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 || *bw != c->bw;
+}
+
+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)
+{
+ Client *c;
+
+ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol) - 1);
+ if (m->lt[m->sellt]->arrange) {
+ m->lt[m->sellt]->arrange(m);
+ } else {
+ for (c = selmon->clients; c; c = c->next) {
+ if (ISVISIBLE(c) && c->bw == 0)
+ resize(c, c->x, c->y, c->w - 2 * borderpx, c->h - 2 * borderpx, borderpx, 0);
+ }
+ }
+}
+
+void
+attach(Client *c)
+{
+ c->next = c->mon->clients;
+ c->mon->clients = c;
+}
+void
+attachBelow(Client *c)
+{
+ /* If there is nothing on the monitor or the selected client is floating, attach as normal */
+ if (c->mon->sel == NULL || c->mon->sel == c || c->mon->sel->isfloating) {
+ attach(c);
+ return;
+ }
+
+ /* Set the new client's next property to the same as the currently selected clients next */
+ c->next = c->mon->sel->next;
+ /* Set the currently selected clients next property to the new client */
+ c->mon->sel->next = c;
+
+}
+
+void
+attachstack(Client *c)
+{
+ c->snext = c->mon->stack;
+ c->mon->stack = c;
+}
+
+void
+bartab(Monitor *m, int offx, int sw)
+{
+ Client *c;
+ int i, x, w, clientsnmaster = 0, clientsnstack = 0, clientsnfloating = 0;
+
+ for (i = 0, c = m->clients; c; c = c->next) {
+ if (!ISVISIBLE(c))
+ continue;
+ if (c->isfloating) {
+ clientsnfloating++;
+ continue;
+ }
+ if (i < m->nmaster)
+ clientsnmaster++;
+ else
+ clientsnstack++;
+ i++;
+ }
+
+ for (c = m->clients, i = 0; c; c = c->next) {
+ if (!ISVISIBLE(c))
+ continue;
+
+ x = offx + (((m->mw - offx - sw) / (clientsnmaster + clientsnstack + clientsnfloating)) * i);
+ w = (m->mw - offx - sw) / (clientsnmaster + clientsnstack + clientsnfloating);
+
+ drw_setscheme(drw, scheme[m->sel == c ? SchemeSel : SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, c->name, 0);
+
+ /* Floating win indicator */
+ if (c->isfloating) drw_rect(drw, x + 2, 2, 5, 5, 0, 0);
+
+ /* Optional borders between tabs */
+ if (barborders) {
+ XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBorder].pixel);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, 0, 1, bh);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x + w - 1, 0, 1, bh);
+ }
+
+ i++;
+ }
+}
+
+void
+bstack(Monitor *m)
+{
+ int w, h, mh, mx, tx, ty, tw, bw;
+ unsigned int i, n, r, g = gaps * m->enablegaps;
+ Client *c;
+
+ for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++);
+ if (n == 0)
+ return;
+
+ if (n == 1)
+ bw = 0;
+ else
+ bw = borderpx;
+
+ if (n > m->nmaster) {
+ mh = m->nmaster ? m->mfact * (m->wh + g) : 0;
+ tw = m->ww / (n - m->nmaster);
+ ty = m->wy + mh;
+ } else {
+ mh = m->wh;
+ tw = m->ww;
+ ty = m->wy;
+ }
+ for (i = mx = 0, tx = m->wx + g, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) {
+ if (i < m->nmaster) {
+ r = MIN(n, m->nmaster) - i;
+ w = (m->ww - mx - g - g * (r - 1)) / r;
+ resize(c, m->wx + mx + g, m->wy + g, w - (2 * bw) - g, mh - (2 * bw) - (2 * g), bw, 0);
+ mx += WIDTH(c) + g;
+ } else {
+ r = n - i;
+ h = m->wh - mh - g;
+ resize(c, tx, ty, tw - (2 * bw) - ((r == 1) ? 2 : 1) * g, h - (2 * bw), bw, 0);
+ if (tw != m->ww)
+ tx += WIDTH(c) + g;
+ }
+ }
+}
+
+void
+buttonpress(XEvent *e)
+{
+ unsigned int i, x, click;
+ Arg arg = {0};
+ Client *c;
+ Monitor *m;
+ XButtonPressedEvent *ev = &e->xbutton;
+
+ click = ClkRootWin;
+ /* focus monitor if necessary */
+ if ((m = wintomon(ev->window)) && m != selmon) {
+ unfocus(selmon->sel, 1);
+ selmon = m;
+ focus(NULL);
+ }
+ if (ev->window == selmon->barwin) {
+ i = x = 0;
+ do
+ x += TEXTW(tags[i]);
+ while (ev->x >= x && ++i < LENGTH(tags));
+ if (i < LENGTH(tags)) {
+ click = ClkTagBar;
+ arg.ui = 1 << i;
+ } else if (ev->x < x + TEXTW(selmon->ltsymbol))
+ click = ClkLtSymbol;
+ else if (ev->x > selmon->ww - (int)TEXTW(stext))
+ click = ClkStatusText;
+ else
+ click = ClkWinTitle;
+ } else if ((c = wintoclient(ev->window))) {
+ focus(c);
+ restack(selmon);
+ XAllowEvents(dpy, ReplayPointer, CurrentTime);
+ click = ClkClientWin;
+ }
+ for (i = 0; i < LENGTH(buttons); i++)
+ if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button
+ && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state))
+ buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg);
+}
+
+void
+checkotherwm(void)
+{
+ xerrorxlib = XSetErrorHandler(xerrorstart);
+ /* this causes an error if some other window manager is running */
+ XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XSync(dpy, False);
+}
+
+void
+cleanup(void)
+{
+ Arg a = {.ui = ~0};
+ Layout foo = { "", NULL };
+ Monitor *m;
+ size_t i;
+
+ view(&a);
+ selmon->lt[selmon->sellt] = &foo;
+ for (m = mons; m; m = m->next)
+ while (m->stack)
+ unmanage(m->stack, 0);
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ while (mons)
+ cleanupmon(mons);
+ for (i = 0; i < CurLast; i++)
+ drw_cur_free(drw, cursor[i]);
+ for (i = 0; i < LENGTH(colors); i++)
+ free(scheme[i]);
+ free(scheme);
+ XDestroyWindow(dpy, wmcheckwin);
+ drw_free(drw);
+ XSync(dpy, False);
+ XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+}
+
+void
+cleanupmon(Monitor *mon)
+{
+ Monitor *m;
+
+ if (mon == mons)
+ mons = mons->next;
+ else {
+ for (m = mons; m && m->next != mon; m = m->next);
+ m->next = mon->next;
+ }
+ XUnmapWindow(dpy, mon->barwin);
+ XDestroyWindow(dpy, mon->barwin);
+ free(mon);
+}
+
+void
+clientmessage(XEvent *e)
+{
+ XClientMessageEvent *cme = &e->xclient;
+ Client *c = wintoclient(cme->window);
+
+ if (!c)
+ return;
+ if (cme->message_type == netatom[NetWMState]) {
+ if (cme->data.l[1] == netatom[NetWMFullscreen]
+ || cme->data.l[2] == netatom[NetWMFullscreen])
+ setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */
+ || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen)));
+ } else if (cme->message_type == netatom[NetActiveWindow]) {
+ if (c != selmon->sel && !c->isurgent)
+ seturgent(c, 1);
+ }
+}
+
+void
+configure(Client *c)
+{
+ XConfigureEvent ce;
+
+ ce.type = ConfigureNotify;
+ ce.display = dpy;
+ ce.event = c->win;
+ ce.window = c->win;
+ ce.x = c->x;
+ ce.y = c->y;
+ ce.width = c->w;
+ ce.height = c->h;
+ ce.border_width = c->bw;
+ ce.above = None;
+ ce.override_redirect = False;
+ XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);
+}
+
+void
+configurenotify(XEvent *e)
+{
+ Monitor *m;
+ Client *c;
+ XConfigureEvent *ev = &e->xconfigure;
+ int dirty;
+
+ /* TODO: updategeom handling sucks, needs to be simplified */
+ if (ev->window == root) {
+ dirty = (sw != ev->width || sh != ev->height);
+ sw = ev->width;
+ sh = ev->height;
+ if (updategeom() || dirty) {
+ drw_resize(drw, sw, bh);
+ updatebars();
+ for (m = mons; m; m = m->next) {
+ for (c = m->clients; c; c = c->next)
+ if (c->isfullscreen)
+ resizeclient(c, m->mx, m->my, m->mw, m->mh, 0);
+ XMoveResizeWindow(dpy, m->barwin, m->wx + m->sp, m->by + m->vp, m->ww - 2 * m->sp, 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->enablegaps = enablegaps;
+ m->sp = gaps * enablegaps;
+ m->vp = ((topbar == 1) ? gaps : - gaps) * enablegaps;
+ m->lt[0] = &layouts[0];
+ m->lt[1] = &layouts[1 % LENGTH(layouts)];
+ strncpy(m->ltsymbol, layouts[0].symbol, sizeof(m->ltsymbol) - 1);
+ return m;
+}
+
+void
+cyclelayout(const Arg *arg)
+{
+ Layout *l;
+ for (l = (Layout *)layouts; l != selmon->lt[selmon->sellt]; l++);
+ if (arg->i > 0) {
+ if (l->symbol && (l + 1)->symbol)
+ setlayout(&((Arg) {
+ .v = (l + 1)
+ }));
+ else
+ setlayout(&((Arg) {
+ .v = layouts
+ }));
+ } else {
+ if (l != layouts && (l - 1)->symbol)
+ setlayout(&((Arg) {
+ .v = (l - 1)
+ }));
+ else
+ setlayout(&((Arg) {
+ .v = &layouts[LENGTH(layouts) - 2]
+ }));
+ }
+}
+
+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, tx = 0;
+ int boxs = drw->fonts->h / 9;
+ int boxw = drw->fonts->h / 6 + 2;
+ unsigned int i, occ = 0, urg = 0, correct = 0;
+ char xcape[2], *ts = stext, *tp = stext, ctmp;
+ Client *c;
+
+ for (xcape[1] = '\0'; *ts != '\0'; ts++) {
+ if (*ts <= LENGTH(colors)) {
+ xcape[0] = *ts;
+ correct += TEXTW(xcape) - lrpad;
+ }
+ }
+ ts = stext;
+
+ if (!m->showbar)
+ return;
+
+ /* draw status first so it can be overdrawn by tags later */
+ drw_setscheme(drw, scheme[SchemeNorm]);
+
+ /* 2px right padding + correction for escape sequences + padding */
+ tw = TEXTW(stext) - lrpad + 2 - correct + 2 * m->sp;
+ while (1) {
+ if ((unsigned int)*ts > LENGTH(colors)) {
+ ts++;
+ continue;
+ }
+ ctmp = *ts;
+ *ts = '\0';
+ drw_text(drw, m->ww - tw + tx, 0, tw - tx, bh, 0, tp, 0);
+ tx += TEXTW(tp) - lrpad;
+ if (ctmp == '\0')
+ break;
+ drw_setscheme(drw, scheme[(unsigned int)(ctmp - 1)]);
+ *ts = ctmp;
+ tp = ++ts;
+ }
+
+ for (c = m->clients; c; c = c->next) {
+ occ |= c->tags;
+ if (c->isurgent)
+ urg |= c->tags;
+ }
+ x = 0;
+ for (i = 0; i < LENGTH(tags); i++) {
+ w = TEXTW(tags[i]);
+ drw_setscheme(drw, (m->tagset[m->seltags] & 1 << i ? tagscheme[i] : scheme[SchemeNorm]));
+ drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i);
+ if (occ & 1 << i)
+ drw_rect(drw, x + boxs, boxs, boxw, boxw,
+ m == selmon && selmon->sel && selmon->sel->tags & 1 << i,
+ urg & 1 << i);
+ x += w;
+ }
+ w = TEXTW(m->ltsymbol);
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0);
+
+ drw_rect(drw, x, 0, m->ww - tw - x, bh, 1, 1);
+ if ((w = m->ww - tw - x) > bh)
+ bartab(m, x, tw + 5);
+ 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
+load_xresources(void)
+{
+ Display *display;
+ char *resm;
+ XrmDatabase db;
+ ResourcePref *p;
+
+ display = XOpenDisplay(NULL);
+ resm = XResourceManagerString(display);
+ if (!resm)
+ return;
+
+ db = XrmGetStringDatabase(resm);
+ for (p = resources; p < resources + LENGTH(resources); p++)
+ resource_load(db, p->name, p->type, p->dst);
+ XCloseDisplay(display);
+}
+
+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);
+ 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);
+ if ( attachbelow )
+ attachBelow(c);
+ else
+ 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
+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, c->bw, 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;
+}
+
+void
+quitprompt(const Arg *arg)
+{
+ char *cmd;
+ size_t max = 66;
+
+ cmd = malloc(sizeof(*cmd) * max);
+ strcpy(cmd, "printf 'no\\nyes\\nrestart' | dmenu -i -sb '");
+ strcat(cmd, magenta);
+ strcat(cmd, "' -p 'Quit DWM?'");
+ FILE *pp = popen(cmd, "r");
+ free(cmd);
+
+ if (pp != NULL) {
+ char buf[1024];
+ if (fgets(buf, sizeof(buf), pp) == NULL) {
+ fprintf(stderr, "Quitprompt: Error reading pipe!\n");
+ return;
+ }
+ if (strcmp(buf, "yes\n") == 0) {
+ pclose(pp);
+ restart = 0;
+ quit(NULL);
+ } else if (strcmp(buf, "restart\n") == 0) {
+ pclose(pp);
+ restart = 1;
+ quit(NULL);
+ } else if (strcmp(buf, "no\n") == 0) {
+ pclose(pp);
+ return;
+ }
+ }
+}
+
+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 bw, int interact)
+{
+ if (applysizehints(c, &x, &y, &w, &h, &bw, interact))
+ resizeclient(c, x, y, w, h, bw);
+}
+
+void
+resizeclient(Client *c, int x, int y, int w, int h, int bw)
+{
+ 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;
+ c->oldbw = c->bw;
+ c->bw = wc.border_width = 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, c->bw, 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
+resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
+{
+ char *sdst = NULL;
+ int *idst = NULL;
+ float *fdst = NULL;
+
+ sdst = dst;
+ idst = dst;
+ fdst = dst;
+
+ char fullname[256];
+ char *type;
+ XrmValue ret;
+
+ snprintf(fullname, sizeof(fullname), "%s.%s", "dwm", name);
+ fullname[sizeof(fullname) - 1] = '\0';
+
+ XrmGetResource(db, fullname, "*", &type, &ret);
+ if (!(ret.addr == NULL || strncmp("String", type, 64))) {
+ switch (rtype) {
+ case STRING:
+ strcpy(sdst, ret.addr);
+ break;
+ case INTEGER:
+ *idst = strtoul(ret.addr, NULL, 10);
+ break;
+ case FLOAT:
+ *fdst = strtof(ret.addr, NULL);
+ break;
+ }
+ }
+}
+
+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 */
+ if ( attachbelow )
+ attachBelow(c);
+ else
+ 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->isfloating = 1;
+ resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh, 0);
+ 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->x = c->oldx;
+ c->y = c->oldy;
+ c->w = c->oldw;
+ c->h = c->oldh;
+ c->bw = c->oldbw;
+ resizeclient(c, c->x, c->y, c->w, c->h, c->bw);
+ 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) - 1);
+ 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);
+
+ /* handle SIGUSR1 for restart */
+ signal(SIGUSR1, sigrestart);
+
+ /* init screen */
+ screen = DefaultScreen(dpy);
+ sw = DisplayWidth(dpy, screen);
+ sh = DisplayHeight(dpy, screen);
+ root = RootWindow(dpy, screen);
+ xinitvisual();
+ drw = drw_create(dpy, screen, root, sw, sh, visual, depth, cmap);
+ 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 */
+ if (LENGTH(tags) > LENGTH(tagsel))
+ die("too few color schemes for the tags");
+ scheme = ecalloc(LENGTH(colors), sizeof(Clr *));
+ for (i = 0; i < LENGTH(colors); i++)
+ scheme[i] = drw_scm_create(drw, colors[i], alphas[i], 3);
+ tagscheme = ecalloc(LENGTH(tagsel), sizeof(Clr *));
+ for (i = 0; i < LENGTH(tagsel); i++)
+ tagscheme[i] = drw_scm_create(drw, tagsel[i], tagalpha, 2);
+ /* 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, c->bw, 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
+sigrestart(int unused)
+{
+ restart = 1;
+ quit(NULL);
+}
+
+/*
+void
+spawn(const Arg *arg)
+{
+ if (fork() == 0) {
+ if (dpy)
+ close(ConnectionNumber(dpy));
+ setsid();
+ execvp(((char **)arg->v)[0], (char **)arg->v);
+ die("dwm: execvp '%s' failed:", ((char **)arg->v)[0]);
+ }
+}
+*/
+
+void
+tag(const Arg *arg)
+{
+ if (selmon->sel && arg->ui & TAGMASK) {
+ selmon->sel->tags = arg->ui & TAGMASK;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+tagmon(const Arg *arg)
+{
+ if (!selmon->sel || !mons->next)
+ return;
+ sendmon(selmon->sel, dirtomon(arg->i));
+}
+
+void
+tile(Monitor *m)
+{
+ unsigned int i, n, h, r, g = gaps * m->enablegaps, mw, my, ty, bw;
+ Client *c;
+
+ for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++);
+ if (n == 0)
+ return;
+
+ if (n == 1)
+ bw = 0;
+ else
+ bw = borderpx;
+
+ if (n > m->nmaster)
+ mw = m->nmaster ? (m->ww + g) * m->mfact : 0;
+ else
+ mw = m->ww - g;
+ for (i = 0, my = ty = g, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) {
+ if (i < m->nmaster) {
+ r = MIN(n, m->nmaster) - i;
+ h = (m->wh - my - g - g * (r - 1)) / r;
+ resize(c, m->wx + g, m->wy + my, mw - (2 * bw) - g, h - (2 * bw), bw, 0);
+ if (my + HEIGHT(c) + g < m->wh)
+ my += HEIGHT(c) + g;
+ } else {
+ r = n - i;
+ h = (m->wh - ty - g - g * (r - 1)) / r;
+ resize(c, m->wx + mw + g, m->wy + ty, m->ww - mw - (2 * bw) - (2 * g), h - (2 * bw), bw, 0);
+ if (ty + HEIGHT(c) + g < m->wh)
+ ty += HEIGHT(c) + g;
+ }
+ }
+}
+
+void
+togglebar(const Arg *arg)
+{
+ selmon->showbar = !selmon->showbar;
+ updatebarpos(selmon);
+ XMoveResizeWindow(dpy, selmon->barwin, selmon->wx + selmon->sp, selmon->by + selmon->vp, selmon->ww - 2 * selmon->sp, 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 - 2 * (borderpx - selmon->sel->bw),
+ selmon->sel->h - 2 * (borderpx - selmon->sel->bw),
+ borderpx, 0);
+ arrange(selmon);
+}
+
+void
+togglefullscr(const Arg *arg)
+{
+ if (selmon->sel)
+ setfullscreen(selmon->sel, !selmon->sel->isfullscreen);
+}
+
+void
+togglegaps(const Arg *arg)
+{
+ selmon->enablegaps = !selmon->enablegaps;
+ selmon->sp = gaps * selmon->enablegaps;
+ selmon->vp = ((topbar == 1) ? gaps : - gaps) * selmon->enablegaps;
+
+ updatebarpos(selmon);
+ XMoveResizeWindow(dpy, selmon->barwin, selmon->wx + selmon->sp, selmon->by + selmon->vp, selmon->ww - 2 * selmon->sp, bh);
+ 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_pixel = 0,
+ .border_pixel = 0,
+ .colormap = cmap,
+ .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->sp, m->by + m->vp, m->ww - 2 * m->sp, bh, 0, depth,
+ InputOutput, visual,
+ CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWColormap | 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 = m->wh - m->sp - bh;
+ m->by = topbar ? m->wy : m->wy + m->wh + m->sp;
+ m->wy = topbar ? m->wy + bh + m->vp : m->wy;
+ } else
+ m->by = -bh - m->vp;
+}
+
+void
+updateclientlist(void)
+{
+ Client *c;
+ Monitor *m;
+
+ XDeleteProperty(dpy, root, netatom[NetClientList]);
+ for (m = mons; m; m = m->next)
+ for (c = m->clients; c; c = c->next)
+ XChangeProperty(dpy, root, netatom[NetClientList],
+ XA_WINDOW, 32, PropModeAppend,
+ (unsigned char *) & (c->win), 1);
+}
+
+int
+updategeom(void)
+{
+ int dirty = 0;
+
+#ifdef XINERAMA
+ if (XineramaIsActive(dpy)) {
+ int i, j, n, nn;
+ Client *c;
+ Monitor *m;
+ XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn);
+ XineramaScreenInfo *unique = NULL;
+
+ for (n = 0, m = mons; m; m = m->next, n++);
+ /* only consider unique geometries as separate screens */
+ unique = ecalloc(nn, sizeof(XineramaScreenInfo));
+ for (i = 0, j = 0; i < nn; i++)
+ if (isuniquegeom(unique, j, &info[i]))
+ memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo));
+ XFree(info);
+ nn = j;
+
+ /* new monitors if nn > n */
+ for (i = n; i < nn; i++) {
+ for (m = mons; m && m->next; m = m->next);
+ if (m)
+ m->next = createmon();
+ else
+ mons = createmon();
+ }
+ for (i = 0, m = mons; i < nn && m; m = m->next, i++)
+ if (i >= n
+ || unique[i].x_org != m->mx || unique[i].y_org != m->my
+ || unique[i].width != m->mw || unique[i].height != m->mh) {
+ dirty = 1;
+ m->num = i;
+ m->mx = m->wx = unique[i].x_org;
+ m->my = m->wy = unique[i].y_org;
+ m->mw = m->ww = unique[i].width;
+ m->mh = m->wh = unique[i].height;
+ updatebarpos(m);
+ }
+ /* removed monitors if n > nn */
+ for (i = nn; i < n; i++) {
+ for (m = mons; m && m->next; m = m->next);
+ while ((c = m->clients)) {
+ dirty = 1;
+ m->clients = c->next;
+ detachstack(c);
+ c->mon = mons;
+ if ( attachbelow )
+ attachBelow(c);
+ else
+ 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)
+{
+ Monitor* m;
+ if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext)))
+ strcpy(stext, "dwm-"VERSION);
+ for (m = mons; m; m = m->next)
+ drawbar(m);
+}
+
+void
+updatetitle(Client *c)
+{
+ if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name))
+ gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name);
+ if (c->name[0] == '\0') /* hack to mark broken clients */
+ strcpy(c->name, broken);
+}
+
+void
+updatewindowtype(Client *c)
+{
+ Atom state = getatomprop(c, netatom[NetWMState]);
+ Atom wtype = getatomprop(c, netatom[NetWMWindowType]);
+
+ if (state == netatom[NetWMFullscreen])
+ setfullscreen(c, 1);
+ if (wtype == netatom[NetWMWindowTypeDialog])
+ c->isfloating = 1;
+}
+
+void
+updatewmhints(Client *c)
+{
+ XWMHints *wmh;
+
+ if ((wmh = XGetWMHints(dpy, c->win))) {
+ if (c == selmon->sel && wmh->flags & XUrgencyHint) {
+ wmh->flags &= ~XUrgencyHint;
+ XSetWMHints(dpy, c->win, wmh);
+ } else
+ c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0;
+ if (wmh->flags & InputHint)
+ c->neverfocus = !wmh->input;
+ else
+ c->neverfocus = 0;
+ XFree(wmh);
+ }
+}
+
+void
+view(const Arg *arg)
+{
+ if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags])
+ return;
+ selmon->seltags ^= 1; /* toggle sel tagset */
+ if (arg->ui & TAGMASK)
+ selmon->tagset[selmon->seltags] = arg->ui & TAGMASK;
+ focus(NULL);
+ arrange(selmon);
+}
+
+Client *
+wintoclient(Window w)
+{
+ Client *c;
+ Monitor *m;
+
+ for (m = mons; m; m = m->next)
+ for (c = m->clients; c; c = c->next)
+ if (c->win == w)
+ return c;
+ return NULL;
+}
+
+Monitor *
+wintomon(Window w)
+{
+ int x, y;
+ Client *c;
+ Monitor *m;
+
+ if (w == root && getrootptr(&x, &y))
+ return recttomon(x, y, 1, 1);
+ for (m = mons; m; m = m->next)
+ if (w == m->barwin)
+ return m;
+ if ((c = wintoclient(w)))
+ return c->mon;
+ return selmon;
+}
+
+/* There's no way to check accesses to destroyed windows, thus those cases are
+ * ignored (especially on UnmapNotify's). Other types of errors call Xlibs
+ * default error handler, which may call exit. */
+int
+xerror(Display *dpy, XErrorEvent *ee)
+{
+ if (ee->error_code == BadWindow
+ || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
+ || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
+ || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
+ || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
+ || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
+ || (ee->request_code == X_GrabButton && ee->error_code == BadAccess)
+ || (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
+ || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable))
+ return 0;
+ fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n",
+ ee->request_code, ee->error_code);
+ return xerrorxlib(dpy, ee); /* may call exit */
+}
+
+int
+xerrordummy(Display *dpy, XErrorEvent *ee)
+{
+ return 0;
+}
+
+/* Startup Error handler to check if another window manager
+ * is already running. */
+int
+xerrorstart(Display *dpy, XErrorEvent *ee)
+{
+ die("dwm: another window manager is already running");
+ return -1;
+}
+
+void
+xinitvisual(void)
+{
+ XVisualInfo *infos;
+ XRenderPictFormat *fmt;
+ int nitems;
+ int i;
+
+ XVisualInfo tpl = {
+ .screen = screen,
+ .depth = 32,
+ .class = TrueColor
+ };
+ long masks = VisualScreenMask | VisualDepthMask | VisualClassMask;
+
+ infos = XGetVisualInfo(dpy, masks, &tpl, &nitems);
+ visual = NULL;
+ for (i = 0; i < nitems; i ++) {
+ fmt = XRenderFindVisualFormat(dpy, infos[i].visual);
+ if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) {
+ visual = infos[i].visual;
+ depth = infos[i].depth;
+ cmap = XCreateColormap(dpy, root, visual, AllocNone);
+ useargb = 1;
+ break;
+ }
+ }
+
+ XFree(infos);
+
+ if (! visual) {
+ visual = DefaultVisual(dpy, screen);
+ depth = DefaultDepth(dpy, screen);
+ cmap = DefaultColormap(dpy, screen);
+ }
+}
+
+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();
+ XrmInitialize();
+ load_xresources();
+ setup();
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath proc exec", NULL) == -1)
+ die("pledge");
+#endif /* __OpenBSD__ */
+ scan();
+ run();
+ cleanup();
+ XCloseDisplay(dpy);
+ if (restart == 1)
+ execlp("dwm", "dwm", NULL);
+
+ return EXIT_SUCCESS;
+}
diff --git a/dwm/dwm.png b/dwm/dwm.png
new file mode 100644
index 0000000..b1f9ba7
--- /dev/null
+++ b/dwm/dwm.png
Binary files differ
diff --git a/dwm/patches/dwm-bartab-6.4.diff b/dwm/patches/dwm-bartab-6.4.diff
new file mode 100644
index 0000000..559070c
--- /dev/null
+++ b/dwm/patches/dwm-bartab-6.4.diff
@@ -0,0 +1,117 @@
+From 7d8116e8f3104168cdcd78bba5107e11025ada6d Mon Sep 17 00:00:00 2001
+From: Ratakor <contact@ratakor.com>
+Date: Sat, 18 Mar 2023 14:29:44 +0100
+Subject: [PATCH] Show the titles of all visible windows in the status bar with
+ optional borders in between each titles
+
+---
+ config.def.h | 1 +
+ dwm.c | 60 ++++++++++++++++++++++++++++++++++++++++++----------
+ 2 files changed, 50 insertions(+), 11 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 9efa774..bbbd75d 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -5,6 +5,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */
+ static const unsigned int snap = 32; /* snap pixel */
+ static const int showbar = 1; /* 0 means no bar */
+ static const int topbar = 1; /* 0 means bottom bar */
++static const int barborders = 1; /* 0 means no borders in bar */
+ static const char *fonts[] = { "monospace:size=10" };
+ static const char dmenufont[] = "monospace:size=10";
+ static const char col_gray1[] = "#222222";
+diff --git a/dwm.c b/dwm.c
+index c2bd871..d58c043 100644
+--- a/dwm.c
++++ b/dwm.c
+@@ -124,6 +124,7 @@ struct Monitor {
+ unsigned int tagset[2];
+ int showbar;
+ int topbar;
++ int barborders;
+ Client *clients;
+ Client *sel;
+ Client *stack;
+@@ -148,6 +149,7 @@ static void arrange(Monitor *m);
+ static void arrangemon(Monitor *m);
+ static void attach(Client *c);
+ static void attachstack(Client *c);
++static void bartab(Monitor *m, int offx, int sw);
+ static void buttonpress(XEvent *e);
+ static void checkotherwm(void);
+ static void cleanup(void);
+@@ -415,6 +417,50 @@ attachstack(Client *c)
+ c->mon->stack = c;
+ }
+
++void
++bartab(Monitor *m, int offx, int sw)
++{
++ Client *c;
++ int i, x, w, clientsnmaster = 0, clientsnstack = 0, clientsnfloating = 0;
++
++ for (i = 0, c = m->clients; c; c = c->next) {
++ if (!ISVISIBLE(c))
++ continue;
++ if (c->isfloating) {
++ clientsnfloating++;
++ continue;
++ }
++ if (i < m->nmaster)
++ clientsnmaster++;
++ else
++ clientsnstack++;
++ i++;
++ }
++
++ for (c = m->clients, i = 0; c; c = c->next) {
++ if (!ISVISIBLE(c))
++ continue;
++
++ x = offx + (((m->mw - offx - sw) / (clientsnmaster + clientsnstack + clientsnfloating)) * i);
++ w = (m->mw - offx - sw) / (clientsnmaster + clientsnstack + clientsnfloating);
++
++ drw_setscheme(drw, scheme[m->sel == c ? SchemeSel : SchemeNorm]);
++ drw_text(drw, x, 0, w, bh, lrpad / 2, c->name, 0);
++
++ /* Floating win indicator */
++ if (c->isfloating) drw_rect(drw, x + 2, 2, 5, 5, 0, 0);
++
++ /* Optional borders between tabs */
++ if (barborders) {
++ XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBorder].pixel);
++ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, 0, 1, bh);
++ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x + w - 1, 0, 1, bh);
++ }
++
++ i++;
++ }
++}
++
+ void
+ buttonpress(XEvent *e)
+ {
+@@ -734,17 +780,9 @@ drawbar(Monitor *m)
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0);
+
+- if ((w = m->ww - tw - x) > bh) {
+- if (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_rect(drw, x, 0, m->ww - tw - x, bh, 1, 1);
++ if ((w = m->ww - tw - x) > bh)
++ bartab(m, x, tw+5);
+ drw_map(drw, m->barwin, 0, 0, m->ww, bh);
+ }
+
+--
+2.40.0
diff --git a/dwm/transient.c b/dwm/transient.c
new file mode 100644
index 0000000..040adb5
--- /dev/null
+++ b/dwm/transient.c
@@ -0,0 +1,42 @@
+/* cc transient.c -o transient -lX11 */
+
+#include <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/dwm/util.c b/dwm/util.c
new file mode 100644
index 0000000..96b82c9
--- /dev/null
+++ b/dwm/util.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include <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/dwm/util.h b/dwm/util.h
new file mode 100644
index 0000000..f633b51
--- /dev/null
+++ b/dwm/util.h
@@ -0,0 +1,8 @@
+/* See LICENSE file for copyright and license details. */
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+
+void die(const char *fmt, ...);
+void *ecalloc(size_t nmemb, size_t size);
diff --git a/dwmblocks/LICENSE b/dwmblocks/LICENSE
new file mode 100644
index 0000000..5024481
--- /dev/null
+++ b/dwmblocks/LICENSE
@@ -0,0 +1,16 @@
+ISC License (ISC)
+
+Copyright 2020 torrinfail
+Copyright 2023 Ratakor
+
+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/dwmblocks/Makefile b/dwmblocks/Makefile
new file mode 100644
index 0000000..1670810
--- /dev/null
+++ b/dwmblocks/Makefile
@@ -0,0 +1,27 @@
+PREFIX ?= /usr/local
+BLOCKS = \\\"${PREFIX}/share/dwmblocks/blocks\\\"
+CFLAGS += -D_DEFAULT_SOURCE -O3
+LDFLAGS += -s -lX11 -lconfig -pthread
+
+all: dwmblocks
+
+dwmblocks: dwmblocks.c
+ ${CC} $< ${CFLAGS} ${LDFLAGS} -o $@
+
+clean:
+ rm -f dwmblocks
+
+install: clean
+ @CFLAGS="$$CFLAGS -DBLOCKS=${BLOCKS}" ${MAKE}
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f dwmblocks ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/dwmblocks
+ mkdir -p ${DESTDIR}${PREFIX}/share/dwmblocks
+ cp -rf blocks ${DESTDIR}${PREFIX}/share/dwmblocks
+ chmod 755 ${DESTDIR}${PREFIX}/share/dwmblocks/blocks/*
+
+uninstall:
+ rm -rf ${DESTDIR}${PREFIX}/bin/dwmblocks\
+ ${DESTDIR}${PREFIX}/share/dwmblocks
+
+.PHONY: all clean install uninstall
diff --git a/dwmblocks/README.md b/dwmblocks/README.md
new file mode 100644
index 0000000..59b53e9
--- /dev/null
+++ b/dwmblocks/README.md
@@ -0,0 +1,54 @@
+dwmblocks
+=========
+Asynchronous modular status bar for dwm written in C.
+
+Notes
+-----
+- **DEPRECATED** see [sb](https://github.com/Ratakor/suckless/tree/master/sb) instead.
+
+- If you don't use my build of dwm you will need the
+ [statuscolors](https://dwm.suckless.org/patches/statuscolors/) patch and
+ [that fix](https://github.com/anurag3301/my-dwm/blob/main/patches/dwm-statucolours-fix.diff)
+ from anurag3301 for the colors to work with dwm.
+
+- Blocks are installed with dwmblocks and can be activated with a config file
+ located in `$XDG_CONFIG_HOME/dwmblocks/config` (if XDG_CONFIG_HOME is not set,
+ `$HOME/.config/dwmblocks/config` is used instead).
+
+- It is possible to manually refresh a block with
+ `kill -sig $(pidof dwmblocks)`, see Configuration for the list of signals.
+ e.g. `kill -34 $(pidof dwmblocks)` will refresh the music block.
+
+- Restart dwmblocks with `kill -10 $(pidof dwmblocks)`.
+
+Installation
+------------
+Make sure to have these dependencies: libX11, libconfig, pthread and run
+
+ # make install
+
+After that you can put dwmblocks in your xinitrc or other startup script to
+have it start with dwm.
+
+Configuration
+-------------
+default configuration:
+```
+# Name Active Signal
+music = false # 34
+cputemp = false # 35
+fanspeed = false # 36
+memory = false # 37
+battery = false # 38
+wifi = false # 39
+ethernet = false # 40
+localip = false # 41
+publicip = false # 42
+volume = false # 43
+mic = false # 44
+news = false # 45
+weather = false # 46
+daypercent = false # 47
+date = false # 48
+time = true # 49
+```
diff --git a/dwmblocks/blocks/sb_battery b/dwmblocks/blocks/sb_battery
new file mode 100755
index 0000000..9f27c86
--- /dev/null
+++ b/dwmblocks/blocks/sb_battery
@@ -0,0 +1,60 @@
+#!/bin/sh
+CAPACITY="$(cat /sys/class/power_supply/BAT0/capacity)"
+STATUS="$(cat /sys/class/power_supply/BAT0/status)"
+
+BAT_0='󰂎'
+BAT_10='󰁺'
+BAT_20='󰁻'
+BAT_30='󰁼'
+BAT_40='󰁽'
+BAT_50='󰁾'
+BAT_60='󰁿'
+BAT_70='󰂀'
+BAT_80='󰂁'
+BAT_90='󰂂'
+BAT_100='󰁹'
+
+CBAT_0='󰢟'
+CBAT_10='󰢜'
+CBAT_20='󰂆'
+CBAT_30='󰂇'
+CBAT_40='󰂈'
+CBAT_50='󰢝'
+CBAT_60='󰂉'
+CBAT_70='󰢞'
+CBAT_80='󰂊'
+CBAT_90='󰂋'
+CBAT_100='󰂅'
+
+if [ "$STATUS" = "Charging" ]; then
+ case $CAPACITY in
+ 99|100) printf '\07%s\04 %s%%\01\n' "$CBAT_100" "$CAPACITY"
+ notify-send "$CBAT_100 $CAPACITY% FULL" ;;
+ 9[0-8]) printf '\07%s\04 %s%%\01\n' "$CBAT_90" "$CAPACITY" ;;
+ 8[0-9]) printf '\07%s\01 %s%%\n' "$CBAT_80" "$CAPACITY" ;;
+ 7[0-9]) printf '\07%s\01 %s%%\n' "$CBAT_70" "$CAPACITY" ;;
+ 6[0-9]) printf '\07%s\01 %s%%\n' "$CBAT_60" "$CAPACITY" ;;
+ 5[0-9]) printf '\07%s\01 %s%%\n' "$CBAT_50" "$CAPACITY" ;;
+ 4[0-9]) printf '\07%s\01 %s%%\n' "$CBAT_40" "$CAPACITY" ;;
+ 3[0-9]) printf '\07%s\01 %s%%\n' "$CBAT_30" "$CAPACITY" ;;
+ 2[0-9]) printf '\07%s\01 %s%%\n' "$CBAT_20" "$CAPACITY" ;;
+ 1[0-9]) printf '\07%s\01 %s%%\n' "$CBAT_10" "$CAPACITY" ;;
+ [0-9]) printf '\07%s\06 %s%%\01\n' "$CBAT_0" "$CAPACITY" ;;
+ esac
+else
+ case $CAPACITY in
+ 100) printf '%s\04 %s%%\01\n' "$BAT_100" "$CAPACITY" ;;
+ 9[0-9]) printf '%s\04 %s%%\01\n' "$BAT_90" "$CAPACITY" ;;
+ 8[0-9]) printf '%s %s%%\n' "$BAT_80" "$CAPACITY" ;;
+ 7[0-9]) printf '%s %s%%\n' "$BAT_70" "$CAPACITY" ;;
+ 6[0-9]) printf '%s %s%%\n' "$BAT_60" "$CAPACITY" ;;
+ 5[0-9]) printf '%s %s%%\n' "$BAT_50" "$CAPACITY" ;;
+ 4[0-9]) printf '%s %s%%\n' "$BAT_40" "$CAPACITY" ;;
+ 3[0-9]) printf '%s\07 %s%%\01\n' "$BAT_30" "$CAPACITY" ;;
+ 2[0-9]) printf '%s\05 %s%%\01\n' "$BAT_20" "$CAPACITY" ;;
+ 1[0-9]) printf '%s\06 %s%%\01\n' "$BAT_10" "$CAPACITY"
+ notify-send "$BAT_10 $CAPACITY% Battery low" ;;
+ [0-9]) printf '%s\06 %s%%\01\n' "$BAT_0" "$CAPACITY"
+ notify-send "$BAT_0 $CAPACITY% Battery VERY LOW" ;;
+ esac
+fi
diff --git a/dwmblocks/blocks/sb_cputemp b/dwmblocks/blocks/sb_cputemp
new file mode 100755
index 0000000..46e95c5
--- /dev/null
+++ b/dwmblocks/blocks/sb_cputemp
@@ -0,0 +1,7 @@
+#!/bin/sh
+ICON=''
+FIRE=''
+TEMP="$(sensors | awk '/CPU/ {gsub("+", " "); gsub("\\..", " "); print $2}')"
+[ "$TEMP" -ge 70 ] &&
+ printf '\06%s %s°C\01\n' "$FIRE" "$TEMP" ||
+ printf '%s %s°C\n' "$ICON" "$TEMP"
diff --git a/dwmblocks/blocks/sb_date b/dwmblocks/blocks/sb_date
new file mode 100755
index 0000000..6b17cd0
--- /dev/null
+++ b/dwmblocks/blocks/sb_date
@@ -0,0 +1,4 @@
+#!/bin/sh
+ICON=''
+DATE="$(date '+%b %d (%a)')"
+printf '%s %s\n' "$ICON" "$DATE"
diff --git a/dwmblocks/blocks/sb_daypercent b/dwmblocks/blocks/sb_daypercent
new file mode 100755
index 0000000..468d5bc
--- /dev/null
+++ b/dwmblocks/blocks/sb_daypercent
@@ -0,0 +1,4 @@
+#!/bin/sh
+ICON=''
+PERCENTAGE="$(((($(date +%H)*60+$(date +%M))*100)/1440))"
+printf '%s %s%%\n' "$ICON" "$PERCENTAGE"
diff --git a/dwmblocks/blocks/sb_ethernet b/dwmblocks/blocks/sb_ethernet
new file mode 100755
index 0000000..bbec12f
--- /dev/null
+++ b/dwmblocks/blocks/sb_ethernet
@@ -0,0 +1,4 @@
+#!/bin/sh
+ICON=
+STATUS=$(ip link | awk '/eth0/ { print $9 }')
+[ "$STATUS" = "UP" ] && printf '%s\n' "$ICON"
diff --git a/dwmblocks/blocks/sb_fanspeed b/dwmblocks/blocks/sb_fanspeed
new file mode 100755
index 0000000..2210db1
--- /dev/null
+++ b/dwmblocks/blocks/sb_fanspeed
@@ -0,0 +1,4 @@
+#!/bin/sh
+ICON=''
+FANSPEED="$(sensors | awk '/fan1/ {print $2}')"
+printf '%s %s RPM\n' "$ICON" "$FANSPEED"
diff --git a/dwmblocks/blocks/sb_localip b/dwmblocks/blocks/sb_localip
new file mode 100755
index 0000000..b0ad6bc
--- /dev/null
+++ b/dwmblocks/blocks/sb_localip
@@ -0,0 +1,4 @@
+#!/bin/sh
+ICON='ﯱ'
+IP="$(ip addr | grep -e "inet " | awk 'NR==2' | sed 's/^.*inet.//g; s/\/.*//g')"
+printf '%s %s\n' "$ICON" "$IP"
diff --git a/dwmblocks/blocks/sb_memory b/dwmblocks/blocks/sb_memory
new file mode 100755
index 0000000..ffa4a1c
--- /dev/null
+++ b/dwmblocks/blocks/sb_memory
@@ -0,0 +1,4 @@
+#!/bin/sh
+ICON='ﳔ'
+MEMORY="$(free -h | awk '/Mem/ {gsub("i", " "); print $3"/"$2 }')"
+printf '%s %s\n' "$ICON" "$MEMORY"
diff --git a/dwmblocks/blocks/sb_mic b/dwmblocks/blocks/sb_mic
new file mode 100755
index 0000000..52f087d
--- /dev/null
+++ b/dwmblocks/blocks/sb_mic
@@ -0,0 +1,7 @@
+#!/bin/sh
+ICON_MUTED=''
+ICON_UNMUTED=''
+
+wpctl get-volume @DEFAULT_SOURCE@ | grep -q MUTED &&
+ printf '\05%s\01\n' "$ICON_MUTED" ||
+ printf '%s\n' "$ICON_UNMUTED"
diff --git a/dwmblocks/blocks/sb_music b/dwmblocks/blocks/sb_music
new file mode 100755
index 0000000..bbcda0d
--- /dev/null
+++ b/dwmblocks/blocks/sb_music
@@ -0,0 +1,20 @@
+#!/bin/sh
+[ $(pgrep music) ] 2> /dev/null && exit 1
+
+strip() {
+ tmp="${1##{\"data\":\"}"
+ printf '%s\n' "${tmp%%\",\"request_id\":0,\"error\":\"success\"\}}"
+}
+
+ICON_PAUSE=''
+ICON_PLAY=''
+PAUSE="$(strip "$(printf '{ "command": ["get_property_string", "pause"] }\n'\
+ | socat - /tmp/mpvsocket 2> /dev/null)")"
+TITLE="$(strip "$(printf '{ "command": ["get_property", "media-title"] }\n'\
+ | socat - /tmp/mpvsocket 2> /dev/null)")"
+
+if [ "$PAUSE" = "yes" ]; then
+ printf '%s %s\n' "$ICON_PAUSE" "$TITLE"
+elif [ "$PAUSE" = "no" ]; then
+ printf '%s %s\n' "$ICON_PLAY" "$TITLE"
+fi
diff --git a/dwmblocks/blocks/sb_news b/dwmblocks/blocks/sb_news
new file mode 100755
index 0000000..c67fec3
--- /dev/null
+++ b/dwmblocks/blocks/sb_news
@@ -0,0 +1,3 @@
+#!/bin/sh
+NEWS=$(newsboat -x print-unread | awk '{ if($1>0) print "📰 " $1}')
+printf '%s\n' "$NEWS"
diff --git a/dwmblocks/blocks/sb_publicip b/dwmblocks/blocks/sb_publicip
new file mode 100755
index 0000000..1e10850
--- /dev/null
+++ b/dwmblocks/blocks/sb_publicip
@@ -0,0 +1,5 @@
+#!/bin/sh
+ICON=''
+IP="$(curl -s ifconfig.me)"
+[ -z "$IP" ] && exit 1
+printf '%s %s\n' "$ICON" "$IP"
diff --git a/dwmblocks/blocks/sb_time b/dwmblocks/blocks/sb_time
new file mode 100755
index 0000000..f710095
--- /dev/null
+++ b/dwmblocks/blocks/sb_time
@@ -0,0 +1,13 @@
+#!/bin/sh
+ICON=''
+H="$(date '+%H')"
+M="$(date '+%M')"
+
+case $H in
+2[2-3])
+ printf '\05%s %s:%s\01\n' "$ICON" "$H" "$M" ;;
+0[0-5])
+ printf '\06%s %s:%s\01\n' "$ICON" "$H" "$M" ;;
+*)
+ printf '%s %s:%s\n' "$ICON" "$H" "$M" ;;
+esac
diff --git a/dwmblocks/blocks/sb_volume b/dwmblocks/blocks/sb_volume
new file mode 100755
index 0000000..27023ec
--- /dev/null
+++ b/dwmblocks/blocks/sb_volume
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+trim() {
+ set -f
+ old_ifs=$IFS
+ IFS="Volume: .[MUTED]"
+ # shellcheck disable=SC2086
+ set -- $1
+ IFS=
+ printf '%s\n' "$*"
+ IFS=$old_ifs
+ set +f
+}
+
+VOL="$(wpctl get-volume @DEFAULT_AUDIO_SINK@)"
+
+case $VOL in
+*MUTED*)
+ printf '\05 %.0f%%\01\n' "$(trim "$VOL")" ;;
+*)
+ printf ' %.0f%%\n' "$(trim "$VOL")" ;;
+esac
diff --git a/dwmblocks/blocks/sb_weather b/dwmblocks/blocks/sb_weather
new file mode 100755
index 0000000..77d4b16
--- /dev/null
+++ b/dwmblocks/blocks/sb_weather
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+trimall() {
+ set -f
+ # shellcheck disable=SC2086,SC2048
+ set -- $*
+ printf '%s\n' "$*"
+ set +f
+}
+
+WEATHER="$(curl -s "wttr.in?format=1")"
+[ -z "$WEATHER" ] && exit 1 || WEATHER="$(trimall "$WEATHER")"
+printf '%s\n' "$WEATHER"
diff --git a/dwmblocks/blocks/sb_wifi b/dwmblocks/blocks/sb_wifi
new file mode 100755
index 0000000..3b864d4
--- /dev/null
+++ b/dwmblocks/blocks/sb_wifi
@@ -0,0 +1,13 @@
+#!/bin/sh
+WIFI_FULL_ICON='󰤨'
+WIFI_MID_ICON='󰤢'
+WIFI_LOW_ICON='󰤟'
+NO_WIFI_ICON='󰤯'
+PERCENTAGE="$(awk '/wlan0/ {printf "%d\n", int($3 * 100 / 70)}' /proc/net/wireless)"
+
+case $PERCENTAGE in
+100|9[0-9]|8[0-9]|7[0-9]) printf '%s\n' "$WIFI_FULL_ICON" ;;
+6[0-9]|5[0-9]|4[0-9]|3[0-9]) printf '%s\n' "$WIFI_MID_ICON" ;;
+2[0-9]|1[0-9]|[0-9]) printf '%s\n' "$WIFI_LOW_ICON" ;;
+*) printf '%s\n' "$NO_WIFI_ICON" ;;
+esac
diff --git a/dwmblocks/config.h b/dwmblocks/config.h
new file mode 100644
index 0000000..230e72d
--- /dev/null
+++ b/dwmblocks/config.h
@@ -0,0 +1,25 @@
+#ifndef BLOCKS
+#define BLOCKS "./blocks"
+#endif /* BLOCKS */
+
+static Block blocks[] = {
+ /* Name Command Active Update Interval */
+ { "music", BLOCKS "/sb_music", 0, 0, },
+ { "cputemp", BLOCKS "/sb_cputemp", 0, 10, },
+ { "fanspeed", BLOCKS "/sb_fanspeed", 0, 10, },
+ { "memory", BLOCKS "/sb_memory", 0, 10, },
+ { "battery", BLOCKS "/sb_battery", 0, 30, },
+ { "wifi", BLOCKS "/sb_wifi", 0, 10, },
+ { "ethernet", BLOCKS "/sb_ethernet", 0, 10, },
+ { "localip", BLOCKS "/sb_localip", 0, 3600, },
+ { "publicip", BLOCKS "/sb_publicip", 0, 3600, },
+ { "volume", BLOCKS "/sb_volume", 0, 0, },
+ { "mic", BLOCKS "/sb_mic", 0, 0, },
+ { "news", BLOCKS "/sb_news", 0, 3600, },
+ { "weather", BLOCKS "/sb_weather", 0, 18000, },
+ { "daypercent", BLOCKS "/sb_daypercent", 0, 600, },
+ { "date", BLOCKS "/sb_date", 0, 3600, },
+ { "time", BLOCKS "/sb_time", 1, 60, },
+};
+
+static char delim[] = " \03|\01 ";
diff --git a/dwmblocks/dwmblocks.c b/dwmblocks/dwmblocks.c
new file mode 100644
index 0000000..d43f834
--- /dev/null
+++ b/dwmblocks/dwmblocks.c
@@ -0,0 +1,186 @@
+#include <err.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <X11/Xlib.h>
+#include <libconfig.h>
+
+#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
+#define STRLEN(X) (sizeof(X) - 1)
+#define CONFIG_FILE "dwmblocks/config"
+#define RESTARTSIG SIGUSR1
+#define CMDLENGTH (27 + STRLEN(delim))
+#define STATUSLENGTH (LENGTH(blocks) * CMDLENGTH + 1)
+
+typedef struct {
+ const char *name;
+ const char *command;
+ int active;
+ unsigned int interval;
+} Block;
+
+#include "config.h"
+
+static void getcmd(Block *block);
+static void *blockloop(void *block);
+static void statusloop(void);
+static void termhandler(int sig);
+static void sighandler(int sig);
+static void getcfg(void);
+
+static Display *dpy;
+static pthread_t thr[LENGTH(blocks)];
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static char statusbar[LENGTH(blocks)][CMDLENGTH];
+static char statusstr[STATUSLENGTH];
+static sig_atomic_t running;
+static sig_atomic_t restart;
+
+static void
+getcmd(Block *block)
+{
+ FILE *cmd;
+ char output[CMDLENGTH] = "";
+ size_t len, i = block - blocks;
+
+ cmd = popen(block->command, "r");
+ if (cmd == NULL)
+ return;
+ fgets(output, CMDLENGTH - STRLEN(delim), cmd);
+ pclose(cmd);
+ len = strlen(output);
+ if (len == 0)
+ return;
+ len = output[len - 1] == '\n' ? len - 1 : len;
+ strcpy(output + len, delim);
+
+ pthread_mutex_lock(&mutex);
+ if (strcmp(output, statusbar[i]) == 0) {
+ pthread_mutex_unlock(&mutex);
+ return;
+ }
+
+ memcpy(statusbar[i], output, len + STRLEN(delim));
+ statusbar[i][len + STRLEN(delim)] = '\0';
+ statusstr[0] = '\0';
+ for (i = 0; i < LENGTH(blocks); i++)
+ strcat(statusstr, statusbar[i]);
+ statusstr[strlen(statusstr) - STRLEN(delim)] = '\0';
+ // TODO: can fail ?
+ XStoreName(dpy, DefaultRootWindow(dpy), statusstr);
+ XSync(dpy, False);
+ pthread_mutex_unlock(&mutex);
+}
+
+static void *
+blockloop(void *block)
+{
+ unsigned int interval = ((Block *)block)->interval;
+
+ getcmd(block);
+ if (interval == 0)
+ pthread_exit(NULL);
+
+ while (running) {
+ sleep(interval);
+ getcmd(block);
+ }
+ pthread_exit(NULL);
+}
+
+static void
+statusloop(void)
+{
+ pthread_attr_t attr;
+ size_t i;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ for (i = 0; i < LENGTH(blocks); i++) {
+ statusbar[i][0] = '\0';
+ if (blocks[i].active)
+ pthread_create(thr + i, &attr, blockloop, blocks + i);
+ }
+ pthread_attr_destroy(&attr);
+
+ while (running)
+ pause();
+
+ for (i = 0; i < LENGTH(blocks); i++) {
+ if (blocks[i].active)
+ pthread_cancel(thr[i]);
+ }
+}
+
+static void
+sighandler(int sig)
+{
+ if (blocks[sig - SIGRTMIN].active)
+ getcmd(blocks + sig - SIGRTMIN);
+}
+
+static void
+termhandler(int sig)
+{
+ restart = (sig == RESTARTSIG);
+ running = 0;
+}
+
+static void
+getcfg(void)
+{
+ config_t cfg;
+ char path[PATH_MAX], *env;
+ size_t i;
+
+ env = getenv("XDG_CONFIG_HOME");
+ if (env) {
+ snprintf(path, sizeof(path), "%s/%s", env, CONFIG_FILE);
+ } else {
+ env = getenv("HOME");
+ if (env == NULL)
+ errx(1, "HOME is not defined");
+ snprintf(path, sizeof(path), "%s/.config/%s", env, CONFIG_FILE);
+ }
+
+ config_init(&cfg);
+ if (config_read_file(&cfg, path) == CONFIG_FALSE) {
+ errx(1, "%s:%d: %s", path, config_error_line(&cfg),
+ config_error_text(&cfg));
+ }
+ for (i = 0; i < LENGTH(blocks); i++)
+ config_lookup_bool(&cfg, blocks[i].name, &blocks[i].active);
+ config_destroy(&cfg);
+}
+
+int
+main(void)
+{
+ size_t i;
+
+ dpy = XOpenDisplay(NULL);
+ if (dpy == NULL)
+ errx(1, "Failed to open display");
+ signal(SIGTERM, termhandler);
+ signal(SIGINT, termhandler);
+ signal(RESTARTSIG, termhandler);
+ for (i = 0; i < LENGTH(blocks); i++)
+ signal(SIGRTMIN + i, sighandler);
+
+start:
+ getcfg();
+ restart = 0;
+ running = 1;
+ statusloop();
+ if (restart)
+ goto start;
+
+ XStoreName(dpy, DefaultRootWindow(dpy), "");
+ XCloseDisplay(dpy);
+
+ return 0;
+}
diff --git a/sb/LICENSE b/sb/LICENSE
new file mode 100644
index 0000000..f574440
--- /dev/null
+++ b/sb/LICENSE
@@ -0,0 +1,13 @@
+Copyright © 2023, ratakor <ratakor@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/sb/Makefile b/sb/Makefile
new file mode 100644
index 0000000..2b24142
--- /dev/null
+++ b/sb/Makefile
@@ -0,0 +1,26 @@
+PREFIX ?= /usr/local
+
+OBJ = sb.o blocks.o util.o
+CFLAGS += -O3 -std=c99 -pedantic -Wall -Werror -D_DEFAULT_SOURCE
+LDFLAGS += -s -lX11 -lpthread
+
+all: sb
+
+sb: config.h ${OBJ}
+ ${CC} ${OBJ} ${LDFLAGS} -o $@
+
+config.h:
+ cp config.def.h $@
+
+clean:
+ rm -f sb ${OBJ}
+
+install: all
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f sb ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/sb
+
+uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/sb
+
+.PHONY: all clean install uninstall
diff --git a/sb/README.md b/sb/README.md
new file mode 100644
index 0000000..bee4b32
--- /dev/null
+++ b/sb/README.md
@@ -0,0 +1,61 @@
+sb - statusbar
+==============
+Asynchronous modular status bar for dwm written in C99
+
+![image](/sb.png)
+
+Notes
+-----
+- If you don't use my build of dwm you will need
+ [this patch](https://gist.github.com/Ratakor/1a2ebc9a690dea31c19ac419e9e14138)
+ for the colors to work with dwm. Also you might want to use
+ [this font](https://github.com/Ratakor/dotfiles/raw/master/.local/share/fonts/agave%20regular%20Nerd%20Font%20Complete%20Mono.ttf)
+ as fallback as it supports all the icons.
+
+- sb can be configured on the fly with a config file located in
+ `$XDG_CONFIG_HOME/sb/config` (if XDG_CONFIG_HOME is not set,
+ `$HOME/.config/sb/config` will be used instead).
+
+- It is possible to manually refresh a block with
+ `kill -sig $(pidof sb)`, see Configuration for the list of signals.
+ e.g. `kill -34 $(pidof sb)` will refresh the music block.
+
+- Restart sb with `kill -10 $(pidof sb)`.
+
+Installation
+------------
+Make sure to have libX11 and pthread then run
+
+ # make install
+
+After that you can put sb in your xinitrc or other startup script to have it
+start with dwm.
+
+Configuration
+-------------
+default configuration:
+```
+# Name Active Signal
+music = false # 34
+cputemp = false # 35
+cpu = false # 36
+memory = false # 37
+battery = false # 38
+wifi = false # 39
+netspeed = false # 40
+localip = false # 41
+publicip = false # 42
+volume = false # 43
+mic = false # 44
+news = false # 45
+weather = false # 46
+daypercent = false # 47
+date = false # 48
+time = true # 49
+```
+
+TODO
+----
+- Make async optional and only activated for certain blocks
+- Make icons customizable with config.h and maybe the config file
+- Make colors optional (and customizable) at runtime
diff --git a/sb/blocks.c b/sb/blocks.c
new file mode 100644
index 0000000..128c8de
--- /dev/null
+++ b/sb/blocks.c
@@ -0,0 +1,504 @@
+/* See LICENSE file for license details. */
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <linux/wireless.h>
+
+#include "util.h"
+#define BLOCKS_C
+#include "config.h"
+
+/* don't change any of these without changing the corresponding function */
+#define NEWS_CMD "newsboat -x print-unread"
+#define MIC_CMD "wpctl get-volume @DEFAULT_AUDIO_SOURCE@"
+#define VOLUME_CMD "wpctl get-volume @DEFAULT_AUDIO_SINK@"
+#define PUBLICIP_URL "ifconfig.me"
+#define WEATHER_URL "wttr.in?format=1"
+#define CAPACITY_PATH "/sys/class/power_supply/" BATTERY "/capacity"
+#define STATUS_PATH "/sys/class/power_supply/" BATTERY "/status"
+#define CPUTEMP_PATH "/sys/devices/virtual/thermal/" THERMAL_ZONE "/temp"
+#define OPERSTATE(X) "/sys/class/net/" X "/operstate"
+#define NETSPEED_RX(X) "/sys/class/net/" X "/statistics/rx_bytes"
+#define NETSPEED_TX(X) "/sys/class/net/" X "/statistics/tx_bytes"
+#define MUSIC_PAUSE "{\"command\":[\"get_property_string\",\"pause\"]}\n"
+#define MUSIC_TITLE "{\"command\":[\"get_property\",\"media-title\"]}\n"
+
+#if BASE == 1000
+static const char *const prefix[] = {
+ "", "k", "M", "G", "T", "P", "E", "Z", "Y"
+};
+#elif BASE == 1024
+static const char *const prefix[] = {
+ "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"
+};
+#else
+#error "BASE must be equal to 1000 or 1024"
+#endif /* BASE */
+
+int
+music(char *output)
+{
+ static const struct sockaddr_un addr = {
+ .sun_path = "/tmp/mpvsocket",
+ .sun_family = AF_UNIX
+ };
+ static const char *const properties[] = { MUSIC_PAUSE, MUSIC_TITLE };
+ char buf[2 * OUTPUT_MAX], *start, *end;
+ ssize_t len;
+ int i, fd;
+ bool pause;
+
+ for (i = 0; i < 2; i++) {
+ if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+ warn("socket:");
+ close(fd);
+ return -1;
+ }
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ /* warn("connect '%s':", addr.sun_path); */
+ close(fd);
+ output[0] = '\0';
+ return 1;
+ }
+ if (send(fd, properties[i], strlen(properties[i]), 0) < 0) {
+ warn("send '%s':", properties[i]);
+ close(fd);
+ return -1;
+ }
+ if ((len = recv(fd, buf, STRLEN(buf), MSG_PEEK)) < 0) {
+ /* warn("recv:"); */
+ close(fd);
+ return -1;
+ }
+ buf[len] = '\0';
+
+ if (close(fd) < 0) {
+ warn("close:");
+ return -1;
+ }
+
+ start = buf + STRLEN("{\"data\":\"");
+ if (i == 0) {
+ pause = (strncmp(start, "yes", STRLEN("yes")) == 0);
+ } else {
+ end = strchr(start, ',');
+ if (end) *(end - 1) = '\0';
+ }
+ }
+
+ /* use snprintf to not fail on truncation */
+ if (pause)
+ return snprintf(output, OUTPUT_MAX, MAGENTA" %s"NORM, start);
+ return snprintf(output, OUTPUT_MAX, " %s", start);
+}
+
+int
+cputemp(char *output)
+{
+ intmax_t temp;
+
+ temp = fgetsn(CPUTEMP_PATH);
+ if (temp <= 0)
+ return -1;
+
+ temp /= 1000;
+ if (temp >= 70)
+ return xsnprintf(output, OUTPUT_MAX, RED" %02d°C"NORM, temp);
+ return xsnprintf(output, OUTPUT_MAX, " %02d°C", temp);
+}
+
+int
+cpu(char *output)
+{
+ static long double a[7];
+ long double b[7], sum;
+ FILE *fp;
+
+ memcpy(b, a, sizeof(b));
+ if ((fp = fopen("/proc/stat", "r")) == NULL) {
+ warn("fopen '%s':", "/proc/stat");
+ return -1;
+ }
+
+ /* cpu user nice system idle iowait irq softirq */
+ if (fscanf(fp, "%*s %Lf %Lf %Lf %Lf %Lf %Lf %Lf",
+ &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6])
+ != 7) {
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+
+ 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 -1;
+
+ return xsnprintf(output, OUTPUT_MAX, " %d%%", (int)(100 *
+ ((b[0] + b[1] + b[2] + b[5] + b[6]) -
+ (a[0] + a[1] + a[2] + a[5] + a[6])) / sum));
+}
+
+int
+memory(char *output)
+{
+ FILE *fp;
+ uintmax_t total, free, buffers, cached;
+ double dtotal, used;
+ size_t i, j;
+
+ if ((fp = fopen("/proc/meminfo", "r")) == NULL) {
+ warn("fopen '%s':", "/proc/meminfo");
+ return -1;
+ }
+
+ if (fscanf(fp, "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) {
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+
+ used = (total - free - buffers - cached) * BASE;
+ dtotal = total * BASE;
+ for (i = 0; i < LENGTH(prefix) && used >= BASE; i++)
+ used /= BASE;
+ for (j = 0; j < LENGTH(prefix) && dtotal >= BASE; j++)
+ dtotal /= BASE;
+
+ return xsnprintf(output, OUTPUT_MAX, "ﳔ %.1f%s/%.1f%s",
+ used, prefix[i], dtotal, prefix[j]);
+}
+
+int
+battery(char *output)
+{
+ static const char *const icons[] = {
+ "󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹",
+ "󰢟", "󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅"
+ };
+ const char *icon, *color;
+ FILE *fp;
+ char status[32];
+ intmax_t capacity;
+ bool charging;
+
+ if ((capacity = fgetsn(CAPACITY_PATH)) < 0)
+ return -1;
+
+ if ((fp = fopen(STATUS_PATH, "r")) == NULL) {
+ warn("fopen '%s':", STATUS_PATH);
+ return -1;
+ }
+ if (fgets(status, sizeof(status), fp) == NULL) {
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+
+ charging = (streql(status, "Charging\n"));
+ icon = icons[(capacity / 10) + (charging * LENGTH(icons) / 2)];
+ color = charging ? NORM : "";
+
+ if (capacity >= 90)
+ color = GREEN;
+ else if (!charging) {
+ if (capacity < 40 && capacity >= 30)
+ color = YELLOW;
+ else if (capacity < 30 && capacity >= 20)
+ color = ORANGE;
+ else if (capacity < 20)
+ color = RED;
+ }
+
+ if (charging)
+ return xsnprintf(output, OUTPUT_MAX, YELLOW"%s %s%d%%"NORM,
+ icon, color, capacity);
+ return xsnprintf(output, OUTPUT_MAX, "%s %s%d%%"NORM,
+ icon, color, capacity);
+}
+
+int
+wifi(char *output)
+{
+ char ssid[IW_ESSID_MAX_SIZE + 1] = "";
+ const struct iwreq wreq = {
+ .ifr_name = WIFI,
+ .u.essid.length = sizeof(ssid),
+ .u.essid.pointer = ssid
+ };
+ FILE *fp;
+ char buf[128], *p;
+ int i, fd, quality;
+
+ if ((fp = fopen(OPERSTATE(WIFI), "r")) == NULL) {
+ warn("fopen '%s':", OPERSTATE(WIFI));
+ return -1;
+ }
+ p = fgets(buf, sizeof(buf), fp);
+ fclose(fp);
+ if (p == NULL || !streql(buf, "up\n"))
+ return xsnprintf(output, OUTPUT_MAX, "󰤯");
+ if ((fp = fopen("/proc/net/wireless", "r")) == NULL) {
+ warn("fopen '/proc/net/wireless':");
+ return -1;
+ }
+ /* skip to line 3 */
+ for (i = 0; i < 3; i++) {
+ if ((p = fgets(buf, sizeof(buf), fp)) == NULL)
+ break;
+ }
+ fclose(fp);
+ if (i != 3 || p == NULL || (p = strstr(buf, WIFI)) == NULL)
+ return -1;
+ p += sizeof(WIFI) + 1;
+ if (sscanf(p, "%*d %d %*d", &quality) != 1)
+ return -1;
+ quality = (float)quality / 70 * 100;
+
+ if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ warn("socket 'AF_INET':");
+ return -1;
+ }
+ if (ioctl(fd, SIOCGIWESSID, &wreq) < 0) {
+ warn("ioctl 'SIOCGIWESSID':");
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ if (quality >= 70)
+ return xsnprintf(output, OUTPUT_MAX, "󰤨 %s", ssid);
+ if (quality >= 30)
+ return xsnprintf(output, OUTPUT_MAX, "󰤢 %s", ssid);
+ return xsnprintf(output, OUTPUT_MAX, "󰤟 %s", ssid);
+}
+
+int
+netspeed(char *output)
+{
+ static intmax_t rx, tx;
+ static const char *pathrx, *pathtx;
+ intmax_t tmprx, tmptx;
+ double drx, dtx;
+ size_t i, j;
+
+ if (tx == 0 && rx == 0) {
+ FILE *fp;
+ char buf[4], *p;
+
+ do {
+ if ((fp = fopen(OPERSTATE(WIFI), "r")) != NULL) {
+ p = fgets(buf, sizeof(buf), fp);
+ fclose(fp);
+ if (p != NULL && streql(buf, "up\n")) {
+ pathrx = NETSPEED_RX(WIFI);
+ pathtx = NETSPEED_TX(WIFI);
+ break;
+ }
+ }
+ if ((fp = fopen(OPERSTATE(ETH), "r")) != NULL) {
+ p = fgets(buf, sizeof(buf), fp);
+ fclose(fp);
+ if (p != NULL && streql(buf, "up\n")) {
+ pathrx = NETSPEED_RX(ETH);
+ pathtx = NETSPEED_TX(ETH);
+ break;
+ }
+ }
+ return -1;
+ } while (0);
+ }
+
+ tmprx = rx;
+ tmptx = tx;
+ if ((rx = fgetsn(pathrx)) < 0)
+ return -1;
+ if ((tx = fgetsn(pathtx)) < 0)
+ return -1;
+
+ drx = rx - tmprx;
+ dtx = tx - tmptx;
+ for (i = 0; i < LENGTH(prefix) && drx >= BASE; i++)
+ drx /= BASE;
+ for (j = 0; j < LENGTH(prefix) && dtx >= BASE; j++)
+ dtx /= BASE;
+
+ return xsnprintf(output, OUTPUT_MAX,
+ BLUE "" NORM " %.1f%s%s" ORANGE "" NORM " %.1f%s",
+ drx, prefix[i], DELIM, dtx, prefix[j]);
+}
+
+int
+localip(char *output)
+{
+ struct ifaddrs *ifaddr, *ifa;
+ char host[NI_MAXHOST];
+ int s;
+
+ if (getifaddrs(&ifaddr) < 0) {
+ warn("getifaddrs:");
+ return -1;
+ }
+
+ for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL)
+ continue;
+
+ s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6),
+ host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+ if ((ifa->ifa_addr->sa_family == AF_INET) &&
+ (streql(ifa->ifa_name, ETH) ||
+ streql(ifa->ifa_name, WIFI))) {
+ freeifaddrs(ifaddr);
+ if (s != 0) {
+ warn("getnameinfo: %s", gai_strerror(s));
+ return -1;
+ }
+ return xsnprintf(output, OUTPUT_MAX, "ﯱ %s", host);
+ }
+ }
+
+ freeifaddrs(ifaddr);
+ return -1;
+}
+
+int
+publicip(char *output)
+{
+ char buf[32];
+
+ if (execcmd(buf, sizeof(buf), "curl -s " PUBLICIP_URL) < 0)
+ return -1;
+
+ return xsnprintf(output, OUTPUT_MAX, " %s", buf);
+}
+
+int
+volume(char *output)
+{
+ char buf[32];
+ int vol;
+
+ if (execcmd(buf, sizeof(buf), VOLUME_CMD) < 0)
+ return -1;
+
+ vol = atof(buf + STRLEN("Volume: ")) * 100;
+
+ if (strstr(buf, "MUTED"))
+ return xsnprintf(output, OUTPUT_MAX, CYAN" %d%%"NORM, vol);
+ return xsnprintf(output, OUTPUT_MAX, " %d%%", vol);
+}
+
+int
+mic(char *output)
+{
+ char buf[32];
+
+ if (execcmd(buf, sizeof(buf), MIC_CMD) < 0)
+ return -1;
+
+ if (strstr(buf, "MUTED"))
+ return xsnprintf(output, OUTPUT_MAX, ORANGE""NORM);
+ return xsnprintf(output, OUTPUT_MAX, "");
+}
+
+int
+news(char *output)
+{
+ char buf[32];
+
+ if (execcmd(buf, sizeof(buf), NEWS_CMD) < 0)
+ return -1;
+ if (buf[0] == 'E')
+ return -1;
+
+ return xsnprintf(output, OUTPUT_MAX, "📰 %d", atoi(buf));
+}
+
+int
+weather(char *output)
+{
+ char buf[32];
+ int i, j;
+
+ if (execcmd(buf, sizeof(buf), "curl -s " WEATHER_URL) < 0)
+ return -1;
+
+ for (i = 0, j = 0; j < OUTPUT_MAX; i++) {
+ switch (buf[i]) {
+ case ' ':
+ continue;
+ case '\n':
+ output[j] = '\0';
+ break;
+ case '+':
+ case '-':
+ output[j++] = ' ';
+ default:
+ output[j++] = buf[i];
+ continue;
+ }
+ break;
+ }
+
+ return (j == OUTPUT_MAX) ? -1 : j;
+}
+
+int
+daypercent(char *output)
+{
+ time_t t;
+ int percent;
+
+ t = ltime();
+ percent = ((HOUR(t) * 60 + MINUTE(t)) * 100) / 1440;
+
+ return xsnprintf(output, OUTPUT_MAX, " %d%%", percent);
+}
+
+int
+date(char *output)
+{
+ time_t t;
+ size_t rv;
+
+ t = ltime();
+ rv = strftime(output, OUTPUT_MAX, " %b %d (%a)", gmtime(&t));
+ if (rv == 0) {
+ warn("strftime: String truncation");
+ return -1;
+ }
+ return rv;
+}
+
+int
+sb_time(char *output)
+{
+ time_t t;
+
+ t = ltime();
+ if (HOUR(t) >= 22) {
+ return xsnprintf(output, OUTPUT_MAX, ORANGE" %02d:%02d"NORM,
+ HOUR(t), MINUTE(t));
+ } else if (HOUR(t) <= 5) {
+ return xsnprintf(output, OUTPUT_MAX, RED" %02d:%02d"NORM,
+ HOUR(t), MINUTE(t));
+ }
+ return xsnprintf(output, OUTPUT_MAX, " %02d:%02d",
+ HOUR(t), MINUTE(t));
+}
diff --git a/sb/blocks.h b/sb/blocks.h
new file mode 100644
index 0000000..e3c1b01
--- /dev/null
+++ b/sb/blocks.h
@@ -0,0 +1,40 @@
+/* See LICENSE file for license details. */
+
+#if COLORS
+#define NORM "\x1"
+#define SEL "\x2"
+#define BLUE "\x3"
+#define GREEN "\x4"
+#define ORANGE "\x5"
+#define RED "\x6"
+#define YELLOW "\x7"
+#define MAGENTA "\x8"
+#define CYAN "\x9"
+#else
+#define NORM ""
+#define SEL ""
+#define BLUE ""
+#define GREEN ""
+#define ORANGE ""
+#define RED ""
+#define YELLOW ""
+#define MAGENTA ""
+#define CYAN ""
+#endif /* COLORS */
+
+int music(char *output);
+int cputemp(char *output);
+int cpu(char *output);
+int memory(char *output);
+int battery(char *output);
+int wifi(char *output);
+int netspeed(char *output);
+int localip(char *output);
+int publicip(char *output);
+int volume(char *output);
+int mic(char *output);
+int news(char *output);
+int weather(char *output);
+int daypercent(char *output);
+int date(char *output);
+int sb_time(char *output);
diff --git a/sb/config.def.h b/sb/config.def.h
new file mode 100644
index 0000000..115ef71
--- /dev/null
+++ b/sb/config.def.h
@@ -0,0 +1,39 @@
+/* See LICENSE file for license details. */
+
+#define BATTERY "BAT0"
+#define ETH "eth0"
+#define WIFI "wlan0"
+#define THERMAL_ZONE "thermal_zone0"
+#define BASE 1000
+
+/* dwm colors (NORM SEL BLUE GREEN ORANGE RED YELLOW MAGENTA CYAN) */
+#define COLORS true
+#include "blocks.h"
+
+/* max size of a block */
+#define OUTPUT_MAX 32
+
+/* delimiter between each blocks */
+#define DELIM " " BLUE "|" NORM " "
+
+#ifndef BLOCKS_C
+static Block blocks[] = {
+ /* name func active interval */
+ { "music", music, false, 0, },
+ { "cputemp", cputemp, false, 10, },
+ { "cpu", cpu, false, 10, },
+ { "memory", memory, false, 10, },
+ { "battery", battery, false, 30, },
+ { "wifi", wifi, false, 10, },
+ { "netspeed", netspeed, false, 1, },
+ { "localip", localip, false, 3600, },
+ { "publicip", publicip, false, 3600, },
+ { "volume", volume, false, 0, },
+ { "mic", mic, false, 0, },
+ { "news", news, false, 3600, },
+ { "weather", weather, false, 18000, },
+ { "daypercent", daypercent, false, 1800, },
+ { "date", date, false, 3600, },
+ { "time", sb_time, true, 60, },
+};
+#endif /* BLOCKS_C */
diff --git a/sb/sb.c b/sb/sb.c
new file mode 100644
index 0000000..c0ee5a1
--- /dev/null
+++ b/sb/sb.c
@@ -0,0 +1,244 @@
+/* See LICENSE file for license details. */
+
+#include <ctype.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <X11/Xlib.h>
+
+#include "util.h"
+
+#define VERSION "0.1"
+#define CONFIG_FILE "sb/config"
+#define RESTARTSIG SIGUSR1
+#define CMDLENGTH (OUTPUT_MAX + STRLEN(DELIM))
+#define STATUSLENGTH (LENGTH(blocks) * CMDLENGTH + 1)
+
+typedef struct {
+ const char *name;
+ int (*func)(char *);
+ int active;
+ unsigned int interval;
+} Block;
+
+static void run(Block *block);
+static void *blockloop(void *block);
+static void statusloop(void);
+static void sighandler(int sig);
+static void getcfg(void);
+static void usage(void);
+
+#include "config.h"
+
+static Display *dpy;
+static pthread_t thr[LENGTH(blocks)];
+static pthread_mutex_t mtx;
+static char statusbuf[LENGTH(blocks)][CMDLENGTH];
+static char statusbar[STATUSLENGTH];
+static volatile sig_atomic_t running;
+static volatile sig_atomic_t restart;
+static bool pflag;
+
+void
+run(Block *block)
+{
+ char output[CMDLENGTH] = "";
+ size_t len, i = block - blocks;
+ int rv;
+
+ if ((rv = block->func(output)) < 0)
+ return;
+
+ len = rv;
+ if (len >= OUTPUT_MAX)
+ len = OUTPUT_MAX - 1;
+ strcpy(output + len, DELIM);
+ len += STRLEN(DELIM);
+
+ pthread_mutex_lock(&mtx);
+ if (streql(output, statusbuf[i])) {
+ pthread_mutex_unlock(&mtx);
+ return;
+ }
+
+ memcpy(statusbuf[i], output, len);
+ statusbuf[i][len] = '\0';
+ statusbar[0] = '\0';
+ for (i = 0; i < LENGTH(blocks); i++)
+ strcat(statusbar, statusbuf[i]);
+ statusbar[strlen(statusbar) - STRLEN(DELIM)] = '\0';
+ if (pflag) {
+ puts(statusbar);
+ fflush(stdout);
+ } else {
+ XStoreName(dpy, DefaultRootWindow(dpy), statusbar);
+ XSync(dpy, False);
+ }
+ pthread_mutex_unlock(&mtx);
+}
+
+void *
+blockloop(void *block)
+{
+ unsigned int interval = ((Block *)block)->interval;
+
+ run(block);
+ if (interval == 0)
+ pthread_exit(NULL);
+
+ while (running) {
+ sleep(interval);
+ run(block);
+ }
+ pthread_exit(NULL);
+}
+
+void
+statusloop(void)
+{
+ pthread_attr_t attr;
+ size_t i;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ for (i = 0; i < LENGTH(blocks); i++) {
+ statusbuf[i][0] = '\0';
+ if (blocks[i].active)
+ pthread_create(&thr[i], &attr, blockloop, &blocks[i]);
+ }
+ pthread_attr_destroy(&attr);
+
+ while (running)
+ pause();
+
+ for (i = 0; i < LENGTH(blocks); i++) {
+ if (blocks[i].active)
+ pthread_cancel(thr[i]);
+ }
+}
+
+void
+sighandler(int sig)
+{
+ switch (sig) {
+ case RESTARTSIG:
+ restart = true;
+ case SIGTERM:
+ case SIGINT:
+ case SIGHUP:
+ running = false;
+ break;
+ default:
+ if (blocks[sig - SIGRTMIN].active)
+ run(blocks + sig - SIGRTMIN);
+ }
+}
+
+void
+getcfg(void)
+{
+ FILE *fp;
+ char path[4096], *env;
+ char line[128], *p, *key;
+ size_t i;
+ int val, lc;
+
+ if ((env = getenv("XDG_CONFIG_HOME")))
+ xsnprintf(path, sizeof(path), "%s/" CONFIG_FILE, env);
+ else if ((env = getenv("HOME")))
+ xsnprintf(path, sizeof(path), "%s/.config/" CONFIG_FILE, env);
+ else
+ die(1, "what is this?");
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen '%s':", path);
+ return;
+ }
+
+ for (lc = 1; fgets(line, sizeof(line), fp); lc++) {
+ if (line[0] == '\n' || line[0] == '#')
+ continue;
+ p = strchr(line, '#');
+ if (p != NULL) *p = '\0';
+ key = line;
+ p = strchr(line, '=');
+ if (p == NULL) die(1, "%s:%d: missing '='", path, lc);
+ val = atob(p + 1);
+ if (val < 0) die(1, "%s:%d: wrong value", path, lc);
+ for (p = key; isalpha(*p); p++);
+ *p = '\0';
+
+ for (i = 0; i < LENGTH(blocks); i++) {
+ if (streql(key, blocks[i].name)) {
+ blocks[i].active = val;
+ break;
+ }
+ }
+ }
+
+ fclose(fp);
+}
+
+void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-bpv]\n", progname);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ bool bflag = false;
+ size_t i;
+
+ progname = argv[0];
+ for (i = 1; i < (size_t)argc; i++) {
+ if (streql(argv[i], "-b"))
+ bflag = true;
+ else if (streql(argv[i], "-p"))
+ pflag = true;
+ else if (streql(argv[i], "-v"))
+ die(0, VERSION);
+ else
+ usage();
+ }
+
+ if (bflag && daemon(1, 0) < 0)
+ die(1, "failed to daemonize");
+ if (!pflag && (dpy = XOpenDisplay(NULL)) == NULL)
+ die(1, "cannot open display");
+
+ sa.sa_handler = sighandler;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(RESTARTSIG, &sa, NULL);
+ for (i = 0; i < LENGTH(blocks); i++)
+ sigaction(SIGRTMIN + i, &sa, NULL);
+ pthread_mutex_init(&mtx, NULL);
+
+start:
+ getcfg();
+ restart = false;
+ running = true;
+ statusloop();
+ if (restart)
+ goto start;
+
+ pthread_mutex_destroy(&mtx);
+ if (!pflag) {
+ XStoreName(dpy, DefaultRootWindow(dpy), "");
+ XCloseDisplay(dpy);
+ }
+
+ return 0;
+}
diff --git a/sb/sb.png b/sb/sb.png
new file mode 100644
index 0000000..be6451b
--- /dev/null
+++ b/sb/sb.png
Binary files differ
diff --git a/sb/util.c b/sb/util.c
new file mode 100644
index 0000000..c380aa8
--- /dev/null
+++ b/sb/util.c
@@ -0,0 +1,148 @@
+/* See LICENSE file for license details. */
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "util.h"
+
+char *progname;
+
+static void
+vwarn(const char *fmt, va_list ap)
+{
+ fprintf(stderr, "%s: ", progname);
+
+ if (fmt == NULL) {
+ perror(NULL);
+ return;
+ }
+
+ 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);
+ vwarn(fmt, ap);
+ va_end(ap);
+}
+
+void
+die(int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vwarn(fmt, ap);
+ va_end(ap);
+
+ exit(status);
+}
+
+time_t
+ltime(void)
+{
+ static time_t tz;
+ static bool once;
+
+ if (!once) {
+ const time_t t = time(NULL);
+ const struct tm *const tm = localtime(&t);
+ time_t tz_hour, tz_min;
+
+ tz_hour = tm->tm_hour - HOUR(t);
+ tz_min = tm->tm_min - MINUTE(t);
+ tz = ((tz_hour * 60) + tz_min) * 60;
+ once = true;
+ }
+
+ return time(NULL) + tz;
+}
+
+/* return -1 on failure and warn about truncation (which is also a failure) */
+int
+xsnprintf(char *str, size_t siz, const char *fmt, ...)
+{
+ va_list ap;
+ int rv;
+
+ va_start(ap, fmt);
+ rv = vsnprintf(str, siz, fmt, ap);
+ va_end(ap);
+
+ if (rv < 0) {
+ warn("snprintf:");
+ return -1;
+ } else if ((size_t)rv >= siz) {
+ warn("snprintf: String truncation");
+ return -1;
+ }
+
+ return rv;
+}
+
+/* return -1 on failure */
+int
+execcmd(char *output, size_t siz, const char *cmd)
+{
+ FILE *fp;
+
+ if ((fp = popen(cmd, "r")) == NULL) {
+ warn("popen '%s':", cmd);
+ return -1;
+ }
+ if (fgets(output, siz, fp) == NULL) {
+ pclose(fp);
+ return -1;
+ }
+ pclose(fp);
+
+ return 0;
+}
+
+/* return -1 on failure (or 0) */
+intmax_t
+fgetsn(const char *path)
+{
+ FILE *fp;
+ char buf[4 * sizeof(intmax_t)];
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen '%s':", path);
+ return -1;
+ }
+ if (fgets(buf, sizeof(buf), fp) == NULL) {
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+
+ return atoll(buf);
+}
+
+int
+atob(const char *s)
+{
+ while (isspace(*s)) s++;
+ if (strncasecmp(s, "true", STRLEN("true")) == 0)
+ return true;
+ else if (strncasecmp(s, "false", STRLEN("false")) == 0)
+ return false;
+ else
+ return -1;
+}
diff --git a/sb/util.h b/sb/util.h
new file mode 100644
index 0000000..8d9e0cb
--- /dev/null
+++ b/sb/util.h
@@ -0,0 +1,18 @@
+/* See LICENSE file for license details. */
+
+#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
+#define STRLEN(X) (sizeof(X) - 1)
+#define HOUR(t) ((t / 3600) % 24)
+#define MINUTE(t) ((t / 60) % 60)
+#define SECONDE(t) (t % 60)
+#define streql(s1, s2) (strcmp(s1, s2) == 0)
+
+extern char *progname;
+
+void warn(const char *fmt, ...);
+void die(int status, const char *fmt, ...);
+time_t ltime(void);
+int xsnprintf(char *str, size_t siz, const char *fmt, ...);
+int execcmd(char *output, size_t siz, const char *cmd);
+intmax_t fgetsn(const char *path);
+int atob(const char *s);
diff --git a/scron/LICENSE b/scron/LICENSE
new file mode 100644
index 0000000..fbace2b
--- /dev/null
+++ b/scron/LICENSE
@@ -0,0 +1,23 @@
+MIT/X Consortium License
+
+© 2014-2015 Ari Malinen <ari.malinen@gmail.com>
+© 2014 sin <sin@2f30.org>
+© 2014 Hiltjo Posthuma <hiltjo@codemadness.org>
+
+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/scron/Makefile b/scron/Makefile
new file mode 100644
index 0000000..85d2af4
--- /dev/null
+++ b/scron/Makefile
@@ -0,0 +1,26 @@
+CFLAGS += -std=c99 -Wall -Wextra -pedantic -D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE
+
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/man
+
+BIN = scron
+MAN = scron.1
+
+all: $(BIN)
+
+install: all
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/$(BIN)
+ mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ cp -f $(MAN) $(DESTDIR)$(MANPREFIX)/man1
+ chmod 644 $(DESTDIR)$(MANPREFIX)/man1/$(MAN)
+
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)\
+ $(DESTDIR)$(MANPREFIX)/man1/$(MAN)
+
+clean:
+ rm -f $(BIN)
+
+.PHONY: all install uninstall clean
diff --git a/scron/README b/scron/README
new file mode 100644
index 0000000..08cc127
--- /dev/null
+++ b/scron/README
@@ -0,0 +1,33 @@
+scron
+=====
+Simple cron daemon.
+
+Features
+--------
+* Schedule commands to be run at specified dates and times.
+* Single daemon and configiguration file.
+* Log job output: 'command &>> /var/log/cron.log'.
+* Run job as different user: 'su -c 'command' user'.
+* Log to stdout or syslog.
+* No mail support.
+
+Configuration
+-------------
+Columns:
+ minute, hour, day of month, month, day of week, command
+
+Separator:
+ Any number of tabs or spaces.
+
+Value:
+ * (wildcard), 30 (number), */N (repeat), 1-5 (range), or 1,3,6 (list)
+
+Example of crontab file:
+ # Run updatedb at 6:00 every day
+ 0 6 * * * updatedb
+
+ # Run at 5:30 every business day. Log output to /var/log/backup.log.
+ 30 5 * * 1-5 syncbackup &>> /var/log/backup.log
+
+ # Run as user postmaster at 5:00 every third day of month.
+ 0 5 */3 * * su -c 'mail -s "Hello world" a@b.com' postmaster
diff --git a/scron/TODO b/scron/TODO
new file mode 100644
index 0000000..445185d
--- /dev/null
+++ b/scron/TODO
@@ -0,0 +1,2 @@
+* Detect clock changes
+* DST support?
diff --git a/scron/arg.h b/scron/arg.h
new file mode 100644
index 0000000..4df77a7
--- /dev/null
+++ b/scron/arg.h
@@ -0,0 +1,63 @@
+/*
+ * 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][1]\
+ && argv[0][0] == '-';\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+/* 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(base) (brk_ = 1, estrtol(argv[0], (base)))
+
+#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/scron/queue.h b/scron/queue.h
new file mode 100644
index 0000000..f8f09bf
--- /dev/null
+++ b/scron/queue.h
@@ -0,0 +1,648 @@
+/* $OpenBSD: queue.h,v 1.38 2013/07/03 15:05:21 fgsch Exp $ */
+/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */
+
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues, and circular queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
+#define _Q_INVALIDATE(a) (a) = ((void *)-1)
+#else
+#define _Q_INVALIDATE(a)
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define SLIST_FIRST(head) ((head)->slh_first)
+#define SLIST_END(head) NULL
+#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head))
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_FOREACH(var, head, field) \
+ for((var) = SLIST_FIRST(head); \
+ (var) != SLIST_END(head); \
+ (var) = SLIST_NEXT(var, field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST(head); \
+ (var) && ((tvar) = SLIST_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_INIT(head) { \
+ SLIST_FIRST(head) = SLIST_END(head); \
+}
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ (elm)->field.sle_next = (slistelm)->field.sle_next; \
+ (slistelm)->field.sle_next = (elm); \
+} while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.sle_next = (head)->slh_first; \
+ (head)->slh_first = (elm); \
+} while (0)
+
+#define SLIST_REMOVE_AFTER(elm, field) do { \
+ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \
+} while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ (head)->slh_first = (head)->slh_first->field.sle_next; \
+} while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ if ((head)->slh_first == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } else { \
+ struct type *curelm = (head)->slh_first; \
+ \
+ while (curelm->field.sle_next != (elm)) \
+ curelm = curelm->field.sle_next; \
+ curelm->field.sle_next = \
+ curelm->field.sle_next->field.sle_next; \
+ _Q_INVALIDATE((elm)->field.sle_next); \
+ } \
+} while (0)
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List access methods
+ */
+#define LIST_FIRST(head) ((head)->lh_first)
+#define LIST_END(head) NULL
+#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head))
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_FOREACH(var, head, field) \
+ for((var) = LIST_FIRST(head); \
+ (var)!= LIST_END(head); \
+ (var) = LIST_NEXT(var, field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST(head); \
+ (var) && ((tvar) = LIST_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * List functions.
+ */
+#define LIST_INIT(head) do { \
+ LIST_FIRST(head) = LIST_END(head); \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
+ (listelm)->field.le_next->field.le_prev = \
+ &(elm)->field.le_next; \
+ (listelm)->field.le_next = (elm); \
+ (elm)->field.le_prev = &(listelm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ (elm)->field.le_next = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &(elm)->field.le_next; \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.le_next = (head)->lh_first) != NULL) \
+ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+ (head)->lh_first = (elm); \
+ (elm)->field.le_prev = &(head)->lh_first; \
+} while (0)
+
+#define LIST_REMOVE(elm, field) do { \
+ if ((elm)->field.le_next != NULL) \
+ (elm)->field.le_next->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = (elm)->field.le_next; \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+} while (0)
+
+#define LIST_REPLACE(elm, elm2, field) do { \
+ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \
+ (elm2)->field.le_next->field.le_prev = \
+ &(elm2)->field.le_next; \
+ (elm2)->field.le_prev = (elm)->field.le_prev; \
+ *(elm2)->field.le_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.le_prev); \
+ _Q_INVALIDATE((elm)->field.le_next); \
+} while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type) \
+struct name { \
+ struct type *sqh_first; /* first element */ \
+ struct type **sqh_last; /* addr of last next element */ \
+}
+
+#define SIMPLEQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).sqh_first }
+
+#define SIMPLEQ_ENTRY(type) \
+struct { \
+ struct type *sqe_next; /* next element */ \
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define SIMPLEQ_FIRST(head) ((head)->sqh_first)
+#define SIMPLEQ_END(head) NULL
+#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next)
+
+#define SIMPLEQ_FOREACH(var, head, field) \
+ for((var) = SIMPLEQ_FIRST(head); \
+ (var) != SIMPLEQ_END(head); \
+ (var) = SIMPLEQ_NEXT(var, field))
+
+#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Simple queue functions.
+ */
+#define SIMPLEQ_INIT(head) do { \
+ (head)->sqh_first = NULL; \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (head)->sqh_first = (elm); \
+} while (0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.sqe_next = NULL; \
+ *(head)->sqh_last = (elm); \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+} while (0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+ (listelm)->field.sqe_next = (elm); \
+} while (0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+ (head)->sqh_last = &(head)->sqh_first; \
+} while (0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \
+ if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \
+ == NULL) \
+ (head)->sqh_last = &(elm)->field.sqe_next; \
+} while (0)
+
+/*
+ * XOR Simple queue definitions.
+ */
+#define XSIMPLEQ_HEAD(name, type) \
+struct name { \
+ struct type *sqx_first; /* first element */ \
+ struct type **sqx_last; /* addr of last next element */ \
+ unsigned long sqx_cookie; \
+}
+
+#define XSIMPLEQ_ENTRY(type) \
+struct { \
+ struct type *sqx_next; /* next element */ \
+}
+
+/*
+ * XOR Simple queue access methods.
+ */
+#define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \
+ (unsigned long)(ptr)))
+#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first))
+#define XSIMPLEQ_END(head) NULL
+#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head))
+#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next))
+
+
+#define XSIMPLEQ_FOREACH(var, head, field) \
+ for ((var) = XSIMPLEQ_FIRST(head); \
+ (var) != XSIMPLEQ_END(head); \
+ (var) = XSIMPLEQ_NEXT(head, var, field))
+
+#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = XSIMPLEQ_FIRST(head); \
+ (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \
+ (var) = (tvar))
+
+/*
+ * XOR Simple queue functions.
+ */
+#define XSIMPLEQ_INIT(head) do { \
+ arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.sqx_next = (head)->sqx_first) == \
+ XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \
+ *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+} while (0)
+
+#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \
+ XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+ (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_HEAD(head, field) do { \
+ if (((head)->sqx_first = XSIMPLEQ_XOR(head, \
+ (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \
+ if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \
+ (elm)->field.sqx_next)->field.sqx_next) \
+ == XSIMPLEQ_XOR(head, NULL)) \
+ (head)->sqx_last = \
+ XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+} while (0)
+
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+}
+
+/*
+ * tail queue access methods
+ */
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+#define TAILQ_END(head) NULL
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define TAILQ_EMPTY(head) \
+ (TAILQ_FIRST(head) == TAILQ_END(head))
+
+#define TAILQ_FOREACH(var, head, field) \
+ for((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head); \
+ (var) = TAILQ_NEXT(var, field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST(head); \
+ (var) != TAILQ_END(head) && \
+ ((tvar) = TAILQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for((var) = TAILQ_LAST(head, headname); \
+ (var) != TAILQ_END(head); \
+ (var) = TAILQ_PREV(var, headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST(head, headname); \
+ (var) != TAILQ_END(head) && \
+ ((tvar) = TAILQ_PREV(var, headname, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_INIT(head) do { \
+ (head)->tqh_first = NULL; \
+ (head)->tqh_last = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
+ (head)->tqh_first->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (head)->tqh_first = (elm); \
+ (elm)->field.tqe_prev = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.tqe_next = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+ (elm)->field.tqe_next->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (listelm)->field.tqe_next = (elm); \
+ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ (elm)->field.tqe_next = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ if (((elm)->field.tqe_next) != NULL) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+} while (0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do { \
+ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \
+ (elm2)->field.tqe_next->field.tqe_prev = \
+ &(elm2)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm2)->field.tqe_next; \
+ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \
+ *(elm2)->field.tqe_prev = (elm2); \
+ _Q_INVALIDATE((elm)->field.tqe_prev); \
+ _Q_INVALIDATE((elm)->field.tqe_next); \
+} while (0)
+
+/*
+ * Circular queue definitions.
+ */
+#define CIRCLEQ_HEAD(name, type) \
+struct name { \
+ struct type *cqh_first; /* first element */ \
+ struct type *cqh_last; /* last element */ \
+}
+
+#define CIRCLEQ_HEAD_INITIALIZER(head) \
+ { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
+
+#define CIRCLEQ_ENTRY(type) \
+struct { \
+ struct type *cqe_next; /* next element */ \
+ struct type *cqe_prev; /* previous element */ \
+}
+
+/*
+ * Circular queue access methods
+ */
+#define CIRCLEQ_FIRST(head) ((head)->cqh_first)
+#define CIRCLEQ_LAST(head) ((head)->cqh_last)
+#define CIRCLEQ_END(head) ((void *)(head))
+#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next)
+#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev)
+#define CIRCLEQ_EMPTY(head) \
+ (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head))
+
+#define CIRCLEQ_FOREACH(var, head, field) \
+ for((var) = CIRCLEQ_FIRST(head); \
+ (var) != CIRCLEQ_END(head); \
+ (var) = CIRCLEQ_NEXT(var, field))
+
+#define CIRCLEQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = CIRCLEQ_FIRST(head); \
+ (var) != CIRCLEQ_END(head) && \
+ ((tvar) = CIRCLEQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+
+#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \
+ for((var) = CIRCLEQ_LAST(head); \
+ (var) != CIRCLEQ_END(head); \
+ (var) = CIRCLEQ_PREV(var, field))
+
+#define CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = CIRCLEQ_LAST(head, headname); \
+ (var) != CIRCLEQ_END(head) && \
+ ((tvar) = CIRCLEQ_PREV(var, headname, field), 1); \
+ (var) = (tvar))
+
+/*
+ * Circular queue functions.
+ */
+#define CIRCLEQ_INIT(head) do { \
+ (head)->cqh_first = CIRCLEQ_END(head); \
+ (head)->cqh_last = CIRCLEQ_END(head); \
+} while (0)
+
+#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm)->field.cqe_next; \
+ (elm)->field.cqe_prev = (listelm); \
+ if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (listelm)->field.cqe_next->field.cqe_prev = (elm); \
+ (listelm)->field.cqe_next = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \
+ (elm)->field.cqe_next = (listelm); \
+ (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \
+ if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (listelm)->field.cqe_prev->field.cqe_next = (elm); \
+ (listelm)->field.cqe_prev = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \
+ (elm)->field.cqe_next = (head)->cqh_first; \
+ (elm)->field.cqe_prev = CIRCLEQ_END(head); \
+ if ((head)->cqh_last == CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm); \
+ else \
+ (head)->cqh_first->field.cqe_prev = (elm); \
+ (head)->cqh_first = (elm); \
+} while (0)
+
+#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.cqe_next = CIRCLEQ_END(head); \
+ (elm)->field.cqe_prev = (head)->cqh_last; \
+ if ((head)->cqh_first == CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm); \
+ else \
+ (head)->cqh_last->field.cqe_next = (elm); \
+ (head)->cqh_last = (elm); \
+} while (0)
+
+#define CIRCLEQ_REMOVE(head, elm, field) do { \
+ if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm)->field.cqe_prev; \
+ else \
+ (elm)->field.cqe_next->field.cqe_prev = \
+ (elm)->field.cqe_prev; \
+ if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm)->field.cqe_next; \
+ else \
+ (elm)->field.cqe_prev->field.cqe_next = \
+ (elm)->field.cqe_next; \
+ _Q_INVALIDATE((elm)->field.cqe_prev); \
+ _Q_INVALIDATE((elm)->field.cqe_next); \
+} while (0)
+
+#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \
+ if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \
+ CIRCLEQ_END(head)) \
+ (head)->cqh_last = (elm2); \
+ else \
+ (elm2)->field.cqe_next->field.cqe_prev = (elm2); \
+ if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \
+ CIRCLEQ_END(head)) \
+ (head)->cqh_first = (elm2); \
+ else \
+ (elm2)->field.cqe_prev->field.cqe_next = (elm2); \
+ _Q_INVALIDATE((elm)->field.cqe_prev); \
+ _Q_INVALIDATE((elm)->field.cqe_next); \
+} while (0)
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/scron/scron.1 b/scron/scron.1
new file mode 100644
index 0000000..880debe
--- /dev/null
+++ b/scron/scron.1
@@ -0,0 +1,45 @@
+.Dd Feb 6, 2015
+.Dt SCRON 1
+.Os
+.Sh NAME
+.Nm scron
+.Nd clock daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar file
+.Op Fl n
+.Sh DESCRIPTION
+.Nm
+schedules commands to be run at specified dates and times.
+.Pp
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl f Ar file
+Use the specified
+.Ar file
+instead of the default
+.Ar /etc/crontab .
+.It Fl n
+Do not daemonize.
+.El
+.Sh CONFIGURATION
+Configuration is done by editing the crontab file.
+
+Columns:
+ minute, hour, day of month, month, day of week, command
+
+Separator:
+ Any number of tabs or spaces.
+
+Value:
+ * (wildcard), 30 (number), */N (repeat), 1-5 (range), or 1,3,6 (list)
+.Sh EXAMPLE
+Example of crontab file:
+ # Run updatedb at 6:00 every day
+ 0 6 * * * updatedb
+
+ # Run at 5:30 every business day. Log output to /var/log/backup.log.
+ 30 5 * * 1-5 syncbackup &>> /var/log/backup.log
+
+ # Run as user postmaster at 5:00 every third day of month.
+ 0 5 */3 * * su -c 'mail -s "Hello world" a@b.com' postmaster
diff --git a/scron/scron.c b/scron/scron.c
new file mode 100644
index 0000000..a463318
--- /dev/null
+++ b/scron/scron.c
@@ -0,0 +1,604 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "arg.h"
+#include "queue.h"
+
+#define VERSION "0.4"
+
+#define LEN(x) (sizeof (x) / sizeof *(x))
+
+struct field {
+ enum {
+ ERROR,
+ WILDCARD,
+ NUMBER,
+ RANGE,
+ REPEAT,
+ LIST
+ } type;
+ long *val;
+ int len;
+};
+
+struct ctabentry {
+ struct field min;
+ struct field hour;
+ struct field mday;
+ struct field mon;
+ struct field wday;
+ char *cmd;
+ TAILQ_ENTRY(ctabentry) entry;
+};
+
+struct jobentry {
+ char *cmd;
+ pid_t pid;
+ TAILQ_ENTRY(jobentry) entry;
+};
+
+char *argv0;
+static sig_atomic_t chldreap;
+static sig_atomic_t reload;
+static sig_atomic_t quit;
+static TAILQ_HEAD(, ctabentry) ctabhead = TAILQ_HEAD_INITIALIZER(ctabhead);
+static TAILQ_HEAD(, jobentry) jobhead = TAILQ_HEAD_INITIALIZER(jobhead);
+static char *config = "/etc/crontab";
+static char *pidfile = "/var/run/scron.pid";
+static int nflag;
+
+static void
+loginfo(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (nflag == 0)
+ vsyslog(LOG_INFO, fmt, ap);
+ else
+ vfprintf(stdout, fmt, ap);
+ fflush(stdout);
+ va_end(ap);
+}
+
+static void
+logwarn(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (nflag == 0)
+ vsyslog(LOG_WARNING, fmt, ap);
+ else
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static void
+logerr(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (nflag == 0)
+ vsyslog(LOG_ERR, fmt, ap);
+ else
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static void *
+emalloc(size_t size)
+{
+ void *p;
+ p = malloc(size);
+ if (!p) {
+ logerr("error: out of memory\n");
+ if (nflag == 0)
+ unlink(pidfile);
+ exit(EXIT_FAILURE);
+ }
+ return p;
+}
+
+static char *
+estrdup(const char *s)
+{
+ char *p;
+
+ p = strdup(s);
+ if (!p) {
+ logerr("error: out of memory\n");
+ if (nflag == 0)
+ unlink(pidfile);
+ exit(EXIT_FAILURE);
+ }
+ return p;
+}
+
+static void
+runjob(char *cmd)
+{
+ struct jobentry *je;
+ time_t t;
+ pid_t pid;
+
+ t = time(NULL);
+
+ /* If command is already running, skip it */
+ TAILQ_FOREACH(je, &jobhead, entry) {
+ if (strcmp(je->cmd, cmd) == 0) {
+ loginfo("already running %s pid: %d at %s",
+ je->cmd, je->pid, ctime(&t));
+ return;
+ }
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ logerr("error: failed to fork job: %s time: %s",
+ cmd, ctime(&t));
+ return;
+ } else if (pid == 0) {
+ setsid();
+ loginfo("run: %s pid: %d at %s",
+ cmd, getpid(), ctime(&t));
+ execl("/bin/sh", "/bin/sh", "-c", cmd, (char *)NULL);
+ logerr("error: failed to execute job: %s time: %s",
+ cmd, ctime(&t));
+ _exit(EXIT_FAILURE);
+ } else {
+ je = emalloc(sizeof(*je));
+ je->cmd = estrdup(cmd);
+ je->pid = pid;
+ TAILQ_INSERT_TAIL(&jobhead, je, entry);
+ }
+}
+
+static void
+waitjob(void)
+{
+ struct jobentry *je, *tmp;
+ int status;
+ time_t t;
+ pid_t pid;
+
+ t = time(NULL);
+
+ while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
+ je = NULL;
+ TAILQ_FOREACH(tmp, &jobhead, entry) {
+ if (tmp->pid == pid) {
+ je = tmp;
+ break;
+ }
+ }
+ if (je) {
+ TAILQ_REMOVE(&jobhead, je, entry);
+ free(je->cmd);
+ free(je);
+ }
+ if (WIFEXITED(status) == 1)
+ loginfo("complete: pid: %d returned: %d time: %s",
+ pid, WEXITSTATUS(status), ctime(&t));
+ else if (WIFSIGNALED(status) == 1)
+ loginfo("complete: pid: %d terminated by signal: %s time: %s",
+ pid, strsignal(WTERMSIG(status)), ctime(&t));
+ else if (WIFSTOPPED(status) == 1)
+ loginfo("complete: pid: %d stopped by signal: %s time: %s",
+ pid, strsignal(WSTOPSIG(status)), ctime(&t));
+ }
+}
+
+static int
+isleap(int year)
+{
+ if (year % 400 == 0)
+ return 1;
+ if (year % 100 == 0)
+ return 0;
+ return (year % 4 == 0);
+}
+
+static int
+daysinmon(int mon, int year)
+{
+ int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ if (year < 1900)
+ year += 1900;
+ if (isleap(year))
+ days[1] = 29;
+ return days[mon];
+}
+
+static int
+matchentry(struct ctabentry *cte, struct tm *tm)
+{
+ struct {
+ struct field *f;
+ int tm;
+ int len;
+ } matchtbl[] = {
+ { .f = &cte->min, .tm = tm->tm_min, .len = 60 },
+ { .f = &cte->hour, .tm = tm->tm_hour, .len = 24 },
+ { .f = &cte->mday, .tm = tm->tm_mday, .len = daysinmon(tm->tm_mon, tm->tm_year) },
+ { .f = &cte->mon, .tm = tm->tm_mon, .len = 12 },
+ { .f = &cte->wday, .tm = tm->tm_wday, .len = 7 },
+ };
+ size_t i;
+ int j;
+
+ for (i = 0; i < LEN(matchtbl); i++) {
+ switch (matchtbl[i].f->type) {
+ case WILDCARD:
+ continue;
+ case NUMBER:
+ if (matchtbl[i].f->val[0] == matchtbl[i].tm)
+ continue;
+ break;
+ case RANGE:
+ if (matchtbl[i].f->val[0] <= matchtbl[i].tm)
+ if (matchtbl[i].f->val[1] >= matchtbl[i].tm)
+ continue;
+ break;
+ case REPEAT:
+ if (matchtbl[i].tm > 0) {
+ if (matchtbl[i].tm % matchtbl[i].f->val[0] == 0)
+ continue;
+ } else {
+ if (matchtbl[i].len % matchtbl[i].f->val[0] == 0)
+ continue;
+ }
+ break;
+ case LIST:
+ for (j = 0; j < matchtbl[i].f->len; j++)
+ if (matchtbl[i].f->val[j] == matchtbl[i].tm)
+ break;
+ if (j < matchtbl[i].f->len)
+ continue;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ if (i != LEN(matchtbl))
+ return 0;
+ return 1;
+}
+
+static int
+parsefield(const char *field, long low, long high, struct field *f)
+{
+ int i;
+ char *e1, *e2;
+ const char *p;
+
+ p = field;
+ while (isdigit(*p))
+ p++;
+
+ f->type = ERROR;
+
+ switch (*p) {
+ case '*':
+ if (strcmp(field, "*") == 0) {
+ f->val = NULL;
+ f->len = 0;
+ f->type = WILDCARD;
+ } else if (strncmp(field, "*/", 2) == 0) {
+ f->val = emalloc(sizeof(*f->val));
+ f->len = 1;
+
+ errno = 0;
+ f->val[0] = strtol(field + 2, &e1, 10);
+ if (e1[0] != '\0' || errno != 0 || f->val[0] == 0)
+ break;
+
+ f->type = REPEAT;
+ }
+ break;
+ case '\0':
+ f->val = emalloc(sizeof(*f->val));
+ f->len = 1;
+
+ errno = 0;
+ f->val[0] = strtol(field, &e1, 10);
+ if (e1[0] != '\0' || errno != 0)
+ break;
+
+ f->type = NUMBER;
+ break;
+ case '-':
+ f->val = emalloc(2 * sizeof(*f->val));
+ f->len = 2;
+
+ errno = 0;
+ f->val[0] = strtol(field, &e1, 10);
+ if (e1[0] != '-' || errno != 0)
+ break;
+
+ errno = 0;
+ f->val[1] = strtol(e1 + 1, &e2, 10);
+ if (e2[0] != '\0' || errno != 0)
+ break;
+
+ f->type = RANGE;
+ break;
+ case ',':
+ for (i = 1; isdigit(*p) || *p == ','; p++)
+ if (*p == ',')
+ i++;
+ f->val = emalloc(i * sizeof(*f->val));
+ f->len = i;
+
+ errno = 0;
+ f->val[0] = strtol(field, &e1, 10);
+ if (f->val[0] < low || f->val[0] > high)
+ break;
+
+ for (i = 1; *e1 == ',' && errno == 0; i++) {
+ errno = 0;
+ f->val[i] = strtol(e1 + 1, &e2, 10);
+ e1 = e2;
+ }
+ if (e1[0] != '\0' || errno != 0)
+ break;
+
+ f->type = LIST;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i = 0; i < f->len; i++)
+ if (f->val[i] < low || f->val[i] > high)
+ f->type = ERROR;
+
+ if (f->type == ERROR) {
+ free(f->val);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+freecte(struct ctabentry *cte, int nfields)
+{
+ switch (nfields) {
+ case 6:
+ free(cte->cmd);
+ case 5:
+ free(cte->wday.val);
+ case 4:
+ free(cte->mon.val);
+ case 3:
+ free(cte->mday.val);
+ case 2:
+ free(cte->hour.val);
+ case 1:
+ free(cte->min.val);
+ }
+ free(cte);
+}
+
+static void
+unloadentries(void)
+{
+ struct ctabentry *cte, *tmp;
+
+ for (cte = TAILQ_FIRST(&ctabhead); cte; cte = tmp) {
+ tmp = TAILQ_NEXT(cte, entry);
+ TAILQ_REMOVE(&ctabhead, cte, entry);
+ freecte(cte, 6);
+ }
+}
+
+static int
+loadentries(void)
+{
+ struct ctabentry *cte;
+ FILE *fp;
+ char *line = NULL, *p, *col;
+ int r = 0, y;
+ size_t size = 0;
+ ssize_t len;
+ struct fieldlimits {
+ char *name;
+ long min;
+ long max;
+ struct field *f;
+ } flim[] = {
+ { "min", 0, 59, NULL },
+ { "hour", 0, 23, NULL },
+ { "mday", 1, 31, NULL },
+ { "mon", 1, 12, NULL },
+ { "wday", 0, 6, NULL }
+ };
+ size_t x;
+
+ if ((fp = fopen(config, "r")) == NULL) {
+ logerr("error: can't open %s: %s\n", config, strerror(errno));
+ return -1;
+ }
+
+ for (y = 0; (len = getline(&line, &size, fp)) != -1; y++) {
+ p = line;
+ if (line[0] == '#' || line[0] == '\n' || line[0] == '\0')
+ continue;
+
+ cte = emalloc(sizeof(*cte));
+ flim[0].f = &cte->min;
+ flim[1].f = &cte->hour;
+ flim[2].f = &cte->mday;
+ flim[3].f = &cte->mon;
+ flim[4].f = &cte->wday;
+
+ for (x = 0; x < LEN(flim); x++) {
+ do
+ col = strsep(&p, "\t\n ");
+ while (col && col[0] == '\0');
+
+ if (!col || parsefield(col, flim[x].min, flim[x].max, flim[x].f) < 0) {
+ logerr("error: failed to parse `%s' field on line %d\n",
+ flim[x].name, y + 1);
+ freecte(cte, x);
+ r = -1;
+ break;
+ }
+ }
+
+ if (r == -1)
+ break;
+
+ col = strsep(&p, "\n");
+ if (col)
+ while (col[0] == '\t' || col[0] == ' ')
+ col++;
+ if (!col || col[0] == '\0') {
+ logerr("error: missing `cmd' field on line %d\n",
+ y + 1);
+ freecte(cte, 5);
+ r = -1;
+ break;
+ }
+ cte->cmd = estrdup(col);
+
+ TAILQ_INSERT_TAIL(&ctabhead, cte, entry);
+ }
+
+ if (r < 0)
+ unloadentries();
+
+ free(line);
+ fclose(fp);
+
+ return r;
+}
+
+static void
+reloadentries(void)
+{
+ unloadentries();
+ if (loadentries() < 0)
+ logwarn("warning: discarding old crontab entries\n");
+}
+
+static void
+sighandler(int sig)
+{
+ switch (sig) {
+ case SIGCHLD:
+ chldreap = 1;
+ break;
+ case SIGHUP:
+ reload = 1;
+ break;
+ case SIGTERM:
+ quit = 1;
+ break;
+ }
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, VERSION " (c) 2014-2015\n");
+ fprintf(stderr, "usage: %s [-f file] [-n]\n", argv0);
+ fprintf(stderr, " -f config file\n");
+ fprintf(stderr, " -n do not daemonize\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fp;
+ struct ctabentry *cte;
+ time_t t;
+ struct tm *tm;
+ struct sigaction sa;
+
+ ARGBEGIN {
+ case 'n':
+ nflag = 1;
+ break;
+ case 'f':
+ config = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc > 0)
+ usage();
+
+ if (nflag == 0) {
+ openlog(argv[0], LOG_CONS | LOG_PID, LOG_CRON);
+ if (daemon(1, 0) < 0) {
+ logerr("error: failed to daemonize %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+ if ((fp = fopen(pidfile, "w"))) {
+ fprintf(fp, "%d\n", getpid());
+ fclose(fp);
+ }
+ }
+
+ sa.sa_handler = sighandler;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGCHLD, &sa, NULL);
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ loadentries();
+
+ while (1) {
+ t = time(NULL);
+ sleep(60 - t % 60);
+
+ if (quit == 1) {
+ if (nflag == 0)
+ unlink(pidfile);
+ unloadentries();
+ /* Don't wait or kill forked processes, just exit */
+ break;
+ }
+
+ if (reload == 1 || chldreap == 1) {
+ if (reload == 1) {
+ reloadentries();
+ reload = 0;
+ }
+ if (chldreap == 1) {
+ waitjob();
+ chldreap = 0;
+ }
+ continue;
+ }
+
+ TAILQ_FOREACH(cte, &ctabhead, entry) {
+ t = time(NULL);
+ tm = localtime(&t);
+ if (matchentry(cte, tm) == 1)
+ runjob(cte->cmd);
+ }
+ }
+
+ if (nflag == 0)
+ closelog();
+
+ return EXIT_SUCCESS;
+}
diff --git a/slock/.gitignore b/slock/.gitignore
new file mode 100644
index 0000000..2362192
--- /dev/null
+++ b/slock/.gitignore
@@ -0,0 +1,3 @@
+*.o
+slock
+config.h
diff --git a/slock/LICENSE b/slock/LICENSE
new file mode 100644
index 0000000..9cb2277
--- /dev/null
+++ b/slock/LICENSE
@@ -0,0 +1,25 @@
+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>
+© 2022-2023 Ratakor <ratakor@disroot.org>
+
+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/slock/Makefile b/slock/Makefile
new file mode 100644
index 0000000..b20fd4e
--- /dev/null
+++ b/slock/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/slock/README.md b/slock/README.md
new file mode 100644
index 0000000..50ac07f
--- /dev/null
+++ b/slock/README.md
@@ -0,0 +1,31 @@
+# Ratakor's build of slock
+
+## patch added and changes iirc
+- blur
+- dwm logo
+- dracula theme
+
+slock - simple screen locker
+============================
+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/slock/arg.h b/slock/arg.h
new file mode 100644
index 0000000..0b23c53
--- /dev/null
+++ b/slock/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/slock/config.def.h b/slock/config.def.h
new file mode 100644
index 0000000..6c6113e
--- /dev/null
+++ b/slock/config.def.h
@@ -0,0 +1,38 @@
+/* user and group to drop privileges to */
+static const char *user = "nobody";
+static const char *group = "nobody";
+
+static const char *colorname[NUMCOLS] = {
+ [BACKGROUND] = "white",
+ [INIT] = "#f8f8f2", /* after initialization */
+ [INPUT] = "#bd93f9", /* during input */
+ [FAILED] = "#ff5555", /* wrong password */
+};
+
+/* treat a cleared input like a wrong password (color) */
+static const int failonclear = 1;
+
+static const int logosize = 40;
+static const int logow = 12; /* Grid width and height for right center alignment*/
+static const int logoh = 6;
+
+static XRectangle rectangles[9] = {
+ {0, 3, 1, 3},
+ {1, 3, 2, 1},
+ {0, 5, 8, 1},
+ {3, 0, 1, 5},
+ {5, 3, 1, 2},
+ {7, 3, 1, 2},
+ {8, 3, 4, 1},
+ {9, 4, 1, 2},
+ {11,4, 1, 2},
+};
+
+/*Enable blur*/
+#define BLUR
+/*Set blur radius*/
+static const int blurRadius=5;
+/*Enable Pixelation*/
+//#define PIXELATION
+/*Set pixelation radius*/
+static const int pixelSize=0;
diff --git a/slock/config.mk b/slock/config.mk
new file mode 100644
index 0000000..1ff6f2a
--- /dev/null
+++ b/slock/config.mk
@@ -0,0 +1,40 @@
+# 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
+
+# Xinerama, comment if you don't want it
+XINERAMALIBS = -lXinerama
+XINERAMAFLAGS = -DXINERAMA
+
+# freetype
+FREETYPELIBS = -lXft
+FREETYPEINC = /usr/include/freetype2
+
+# includes and libs
+INCS = -I. -I/usr/include -I${X11INC} -I${FREETYPEINC}
+LIBS = -L/usr/lib -lc -lcrypt -L${X11LIB} -lX11 -lImlib2 ${XINERAMALIBS} ${FREETYPELIBS} -lXext -lXrandr
+
+# flags
+CPPFLAGS += -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -DHAVE_SHADOW_H ${XINERAMAFLAGS}
+CFLAGS += -std=c99 -pedantic -Wall -Ofast ${INCS} ${CPPFLAGS}
+LDFLAGS += -s ${LIBS}
+COMPATSRC = explicit_bzero.c
+
+# 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/slock/explicit_bzero.c b/slock/explicit_bzero.c
new file mode 100644
index 0000000..3e33ca8
--- /dev/null
+++ b/slock/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/slock/slock.1 b/slock/slock.1
new file mode 100644
index 0000000..82cdcd6
--- /dev/null
+++ b/slock/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/slock/slock.c b/slock/slock.c
new file mode 100644
index 0000000..8bd9fa2
--- /dev/null
+++ b/slock/slock.c
@@ -0,0 +1,534 @@
+/* See LICENSE file for license details. */
+#define _XOPEN_SOURCE 500
+#define LENGTH(X) (sizeof X / sizeof X[0])
+#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>
+#ifdef XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+#include <X11/keysym.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <Imlib2.h>
+
+#include "arg.h"
+#include "util.h"
+
+char *argv0;
+
+enum {
+ BACKGROUND,
+ INIT,
+ INPUT,
+ FAILED,
+ NUMCOLS
+};
+
+#include "config.h"
+
+struct lock {
+ int screen;
+ Window root, win;
+ Pixmap pmap;
+ Pixmap bgmap;
+ unsigned long colors[NUMCOLS];
+ unsigned int x, y;
+ unsigned int xoff, yoff, mw, mh;
+ Drawable drawable;
+ GC gc;
+ XRectangle rectangles[LENGTH(rectangles)];
+};
+
+struct xrandr {
+ int active;
+ int evbase;
+ int errbase;
+};
+
+Imlib_Image image;
+
+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
+resizerectangles(struct lock *lock)
+{
+ int i;
+
+ for (i = 0; i < LENGTH(rectangles); i++){
+ lock->rectangles[i].x = (rectangles[i].x * logosize)
+ + lock->xoff + ((lock->mw) / 2) - (logow / 2 * logosize);
+ lock->rectangles[i].y = (rectangles[i].y * logosize)
+ + lock->yoff + ((lock->mh) / 2) - (logoh / 2 * logosize);
+ lock->rectangles[i].width = rectangles[i].width * logosize;
+ lock->rectangles[i].height = rectangles[i].height * logosize;
+ }
+}
+
+static void
+drawlogo(Display *dpy, struct lock *lock, int color)
+{
+ XSetForeground(dpy, lock->gc, lock->colors[color]);
+ XFillRectangles(dpy, lock->win, lock->gc, lock->rectangles, LENGTH(rectangles));
+ XSync(dpy, False);
+}
+
+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++) {
+ if(locks[screen]->bgmap)
+ XSetWindowBackgroundPixmap(dpy, locks[screen]->win, locks[screen]->bgmap);
+ else
+ XSetWindowBackground(dpy, locks[screen]->win, locks[screen]->colors[0]);
+ //XClearWindow(dpy, locks[screen]->win);
+ drawlogo(dpy, locks[screen], color);
+ }
+ 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;
+#ifdef XINERAMA
+ XineramaScreenInfo *info;
+ int n;
+#endif
+
+ if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock))))
+ return NULL;
+
+ lock->screen = screen;
+ lock->root = RootWindow(dpy, lock->screen);
+
+ if(image)
+ {
+ lock->bgmap = XCreatePixmap(dpy, lock->root, DisplayWidth(dpy, lock->screen), DisplayHeight(dpy, lock->screen), DefaultDepth(dpy, lock->screen));
+ imlib_context_set_image(image);
+ imlib_context_set_display(dpy);
+ imlib_context_set_visual(DefaultVisual(dpy, lock->screen));
+ imlib_context_set_colormap(DefaultColormap(dpy, lock->screen));
+ imlib_context_set_drawable(lock->bgmap);
+ imlib_render_image_on_drawable(0, 0);
+ imlib_free_image();
+ }
+ for (i = 0; i < NUMCOLS; i++) {
+ XAllocNamedColor(dpy, DefaultColormap(dpy, lock->screen),
+ colorname[i], &color, &dummy);
+ lock->colors[i] = color.pixel;
+ }
+
+ lock->x = DisplayWidth(dpy, lock->screen);
+ lock->y = DisplayHeight(dpy, lock->screen);
+#ifdef XINERAMA
+ if ((info = XineramaQueryScreens(dpy, &n))) {
+ lock->xoff = info[0].x_org;
+ lock->yoff = info[0].y_org;
+ lock->mw = info[0].width;
+ lock->mh = info[0].height;
+ } else
+#endif
+ {
+ lock->xoff = lock->yoff = 0;
+ lock->mw = lock->x;
+ lock->mh = lock->y;
+ }
+ lock->drawable = XCreatePixmap(dpy, lock->root,
+ lock->x, lock->y, DefaultDepth(dpy, screen));
+ lock->gc = XCreateGC(dpy, lock->root, 0, NULL);
+ XSetLineAttributes(dpy, lock->gc, 1, LineSolid, CapButt, JoinMiter);
+
+ /* init */
+ wa.override_redirect = 1;
+ wa.background_pixel = lock->colors[BACKGROUND];
+ lock->win = XCreateWindow(dpy, lock->root, 0, 0,
+ lock->x, lock->y,
+ 0, DefaultDepth(dpy, lock->screen),
+ CopyFromParent,
+ DefaultVisual(dpy, lock->screen),
+ CWOverrideRedirect | CWBackPixel, &wa);
+ if(lock->bgmap)
+ XSetWindowBackgroundPixmap(dpy, lock->win, lock->bgmap);
+ 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);
+
+ resizerectangles(lock);
+
+ /* 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);
+ drawlogo(dpy, lock, INIT);
+ 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));
+
+ /*Create screenshot Image*/
+ Screen *scr = ScreenOfDisplay(dpy, DefaultScreen(dpy));
+ image = imlib_create_image(scr->width,scr->height);
+ imlib_context_set_image(image);
+ imlib_context_set_display(dpy);
+ imlib_context_set_visual(DefaultVisual(dpy,0));
+ imlib_context_set_drawable(RootWindow(dpy,XScreenNumberOfScreen(scr)));
+ imlib_copy_drawable_to_image(0,0,0,scr->width,scr->height,0,0,1);
+
+#ifdef BLUR
+
+ /*Blur function*/
+ imlib_image_blur(blurRadius);
+#endif // BLUR
+
+#ifdef PIXELATION
+ /*Pixelation*/
+ int width = scr->width;
+ int height = scr->height;
+
+ for(int y = 0; y < height; y += pixelSize)
+ {
+ for(int x = 0; x < width; x += pixelSize)
+ {
+ int red = 0;
+ int green = 0;
+ int blue = 0;
+
+ Imlib_Color pixel;
+ Imlib_Color* pp;
+ pp = &pixel;
+ for(int j = 0; j < pixelSize && j < height; j++)
+ {
+ for(int i = 0; i < pixelSize && i < width; i++)
+ {
+ imlib_image_query_pixel(x+i,y+j,pp);
+ red += pixel.red;
+ green += pixel.green;
+ blue += pixel.blue;
+ }
+ }
+ red /= (pixelSize*pixelSize);
+ green /= (pixelSize*pixelSize);
+ blue /= (pixelSize*pixelSize);
+ imlib_context_set_color(red,green,blue,pixel.alpha);
+ imlib_image_fill_rectangle(x,y,pixelSize,pixelSize);
+ red = 0;
+ green = 0;
+ blue = 0;
+ }
+ }
+
+
+#endif
+ /* 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);
+
+ for (nlocks = 0, s = 0; s < nscreens; s++) {
+ XFreePixmap(dpy, locks[s]->drawable);
+ XFreeGC(dpy, locks[s]->gc);
+ }
+
+ XSync(dpy, 0);
+ XCloseDisplay(dpy);
+ return 0;
+}
diff --git a/slock/util.h b/slock/util.h
new file mode 100644
index 0000000..6f748b8
--- /dev/null
+++ b/slock/util.h
@@ -0,0 +1,2 @@
+#undef explicit_bzero
+void explicit_bzero(void *, size_t);
diff --git a/st/.gitignore b/st/.gitignore
new file mode 100644
index 0000000..e7b3232
--- /dev/null
+++ b/st/.gitignore
@@ -0,0 +1,3 @@
+*.o
+st
+config.h
diff --git a/st/FAQ b/st/FAQ
new file mode 100644
index 0000000..6287a27
--- /dev/null
+++ b/st/FAQ
@@ -0,0 +1,253 @@
+## Why does st not handle utmp entries?
+
+Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task.
+
+
+## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever!
+
+It means that st doesn’t have any terminfo entry on your system. Chances are
+you did not `make install`. If you just want to test it without installing it,
+you can manually run `tic -sx st.info`.
+
+
+## Nothing works, and nothing is said about an unknown terminal!
+
+* Some programs just assume they’re running in xterm i.e. they don’t rely on
+ terminfo. What you see is the current state of the “xterm compliance”.
+* Some programs don’t complain about the lacking st description and default to
+ another terminal. In that case see the question about terminfo.
+
+
+## How do I scroll back up?
+
+* Using a terminal multiplexer.
+ * `st -e tmux` using C-b [
+ * `st -e screen` using C-a ESC
+* Using the excellent tool of [scroll](https://git.suckless.org/scroll/).
+* Using the scrollback [patch](https://st.suckless.org/patches/scrollback/).
+
+
+## I would like to have utmp and/or scroll functionality by default
+
+You can add the absolute path of both programs in your config.h file. You only
+have to modify the value of utmp and scroll variables.
+
+
+## Why doesn't the Del key work in some programs?
+
+Taken from the terminfo manpage:
+
+ If the terminal has a keypad that transmits codes when the keys
+ are pressed, this information can be given. Note that it is not
+ possible to handle terminals where the keypad only works in
+ local (this applies, for example, to the unshifted HP 2621 keys).
+ If the keypad can be set to transmit or not transmit, give these
+ codes as smkx and rmkx. Otherwise the keypad is assumed to
+ always transmit.
+
+In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that
+applications which want to test against keypad keys send these
+sequences.
+
+But buggy applications (like bash and irssi, for example) don't do this. A fast
+solution for them is to use the following command:
+
+ $ printf '\033[?1h\033=' >/dev/tty
+
+or
+ $ tput smkx
+
+In the case of bash, readline is used. Readline has a different note in its
+manpage about this issue:
+
+ enable-keypad (Off)
+ When set to On, readline will try to enable the
+ application keypad when it is called. Some systems
+ need this to enable arrow keys.
+
+Adding this option to your .inputrc will fix the keypad problem for all
+applications using readline.
+
+If you are using zsh, then read the zsh FAQ
+<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/st/LEGACY b/st/LEGACY
new file mode 100644
index 0000000..bf28b1e
--- /dev/null
+++ b/st/LEGACY
@@ -0,0 +1,17 @@
+A STATEMENT ON LEGACY SUPPORT
+
+In the terminal world there is much cruft that comes from old and unsup‐
+ported terminals that inherit incompatible modes and escape sequences
+which noone is able to know, except when he/she comes from that time and
+developed a graphical vt100 emulator at that time.
+
+One goal of st is to only support what is really needed. When you en‐
+counter a sequence which you really need, implement it. But while you
+are at it, do not add the other cruft you might encounter while sneek‐
+ing at other terminal emulators. History has bloated them and there is
+no real evidence that most of the sequences are used today.
+
+
+Christoph Lohmann <20h@r-36.net>
+2012-09-13T07:00:36.081271045+02:00
+
diff --git a/st/LICENSE b/st/LICENSE
new file mode 100644
index 0000000..7d6816c
--- /dev/null
+++ b/st/LICENSE
@@ -0,0 +1,35 @@
+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>
+© 2022-2023 Ratakor <ratakor@disroot.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/st/Makefile b/st/Makefile
new file mode 100644
index 0000000..223ae02
--- /dev/null
+++ b/st/Makefile
@@ -0,0 +1,57 @@
+# st - simple terminal
+# See LICENSE file for copyright and license details.
+.POSIX:
+
+include config.mk
+
+SRC = st.c x.c
+OBJ = $(SRC:.c=.o)
+
+all: options st
+
+options:
+ @echo st build options:
+ @echo "CFLAGS = $(CFLAGS)"
+ @echo "LDFLAGS = $(LDFLAGS)"
+ @echo "CC = $(CC)"
+
+config.h:
+ cp config.def.h config.h
+
+.c.o:
+ $(CC) $(CFLAGS) -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) $(LDFLAGS)
+
+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 -o $(DESTDIR)$(TERMINFO)
+ @echo Please see the README file regarding the terminfo entry of st.
+
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/st
+ rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/st/README.md b/st/README.md
new file mode 100644
index 0000000..7097aa0
--- /dev/null
+++ b/st/README.md
@@ -0,0 +1,53 @@
+# Ratakor's build of st
+
+## patch added and changes iirc
+- alpha
+- Xresources but default to dracula theme
+- scrolling with mouse and alt + j k u d
+- anysize
+- clipboard
+- copyurl (useful for newsboat + dmenuhandler)
+- selectioncolors with alpha
+- workingdir
+- CSI 22, 23
+- reload Xresources with `kill -10 $(pidof st)`
+
+st - simple terminal
+--------------------
+st is a simple terminal emulator for X which sucks less.
+
+
+Requirements
+------------
+In order to build st you need the Xlib header files.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (st is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install st (if
+necessary as root):
+
+ make clean install
+
+
+Running st
+----------
+If you did not install st with make clean install, you must compile
+the st terminfo entry with the following command:
+
+ tic -sx st.info
+
+
+See the man page for additional details.
+
+Also add something like that to your .zshenv / .bash_profile
+export TERMINFO_DIRS="PREFIX/share/terminfo:/usr/share/terminfo"
+where PREFIX is your PREFIX
+
+Credits
+-------
+Based on Aurélien APTEL <aurelien dot aptel at gmail dot com> bt source code.
+
diff --git a/st/TODO b/st/TODO
new file mode 100644
index 0000000..5f74cd5
--- /dev/null
+++ b/st/TODO
@@ -0,0 +1,28 @@
+vt emulation
+------------
+
+* double-height support
+
+code & interface
+----------------
+
+* add a simple way to do multiplexing
+
+drawing
+-------
+* add diacritics support to xdraws()
+ * switch to a suckless font drawing library
+* make the font cache simpler
+* add better support for brightening of the upper colors
+
+bugs
+----
+
+* fix shift up/down (shift selection in emacs)
+* remove DEC test sequence when appropriate
+
+misc
+----
+
+ $ grep -nE 'XXX|TODO' st.c
+
diff --git a/st/arg.h b/st/arg.h
new file mode 100644
index 0000000..a22e019
--- /dev/null
+++ b/st/arg.h
@@ -0,0 +1,50 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][0] == '-'\
+ && argv[0][1];\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ int i_;\
+ for (i_ = 1, brk_ = 0, argv_ = argv;\
+ argv[0][i_] && !brk_;\
+ i_++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][i_];\
+ switch (argc_)
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][i_+1] != '\0')?\
+ (&argv[0][i_+1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][i_+1] != '\0')?\
+ (&argv[0][i_+1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/st/config.def.h b/st/config.def.h
new file mode 100644
index 0000000..a1c424f
--- /dev/null
+++ b/st/config.def.h
@@ -0,0 +1,500 @@
+/* See LICENSE file for copyright and license details. */
+
+/*
+ * appearance
+ *
+ * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
+ */
+static char *font = "monospace:size=10:antialias=true:autohint=true";
+/* Spare fonts */
+static char *font2[] = { "NotoEmoji:size=8:antialias=true:autohint=true" };
+
+static int borderpx = 5;
+
+/*
+ * What program is execed by st depends of these precedence rules:
+ * 1: program passed with -e
+ * 2: scroll and/or utmp
+ * 3: SHELL environment variable
+ * 4: value of shell in /etc/passwd
+ * 5: value of shell in config.h
+ */
+static char *shell = "/bin/sh";
+char *utmp = NULL;
+/* scroll program: to enable use a string like "scroll" */
+char *scroll = NULL;
+char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
+
+/* identification sequence returned in DA and DECID */
+char *vtiden = "\033[?6c";
+
+/* Kerning / character bounding-box multipliers */
+static float cwscale = 1.0;
+static float chscale = 1.0;
+
+/*
+ * word delimiter string
+ *
+ * More advanced example: L" `'\"()[]{}"
+ */
+wchar_t *worddelimiters = L" ";
+
+/* selection timeouts (in milliseconds) */
+static unsigned int doubleclicktimeout = 300;
+static unsigned int tripleclicktimeout = 600;
+
+/* alt screens */
+int allowaltscreen = 1;
+
+/* allow certain non-interactive (insecure) window operations such as:
+ setting the clipboard text */
+int allowwindowops = 0;
+
+/*
+ * draw latency range in ms - from new content/keypress/etc until drawing.
+ * within this range, st draws when content stops arriving (idle). mostly it's
+ * near minlatency, but it waits longer for slow updates to avoid partial draw.
+ * low minlatency will tear/flicker more, as it can "detect" idle too early.
+ */
+static double minlatency = 8;
+static double maxlatency = 33;
+
+/*
+ * blinking timeout (set to 0 to disable blinking) for the terminal blinking
+ * attribute.
+ */
+static unsigned int blinktimeout = 800;
+
+/*
+ * thickness of underline and bar cursors
+ */
+static unsigned int cursorthickness = 2;
+
+/*
+ * bell volume. It must be a value between -100 and 100. Use 0 for disabling
+ * it
+ */
+static int bellvolume = 0;
+
+/* default TERM value */
+char *termname = "st-256color";
+
+/*
+ * spaces per tab
+ *
+ * When you are changing this value, don't forget to adapt the »it« value in
+ * the st.info and appropriately install the st.info in the environment where
+ * you use this st version.
+ *
+ * it#$tabspaces,
+ *
+ * Secondly make sure your kernel is not expanding tabs. When running `stty
+ * -a` »tab0« should appear. You can tell the terminal to not expand tabs by
+ * running following command:
+ *
+ * stty tabs
+ */
+unsigned int tabspaces = 8;
+
+/* bg opacity */
+float alpha = 0.85;
+
+/* Terminal colors (16 first used in escape sequence) */
+static const char *colorname[] = {
+ /* 8 normal colors */
+ [0] = "#191a21", /* black */
+ [1] = "#ff5555", /* red */
+ [2] = "#50fa7b", /* green */
+ [3] = "#f1fa8c", /* yellow */
+ [4] = "#bd93f9", /* blue */
+ [5] = "#ff79c6", /* magenta */
+ [6] = "#8be9fd", /* cyan */
+ [7] = "#abb2bf", /* white */
+
+ /* 8 bright colors */
+ [8] = "#44475a", /* black */
+ [9] = "#ff6e6e", /* red */
+ [10] = "#69ff94", /* green */
+ [11] = "#ffffa5", /* yellow */
+ [12] = "#d6acff", /* blue */
+ [13] = "#ff92df", /* magenta */
+ [14] = "#a4ffff", /* cyan */
+ [15] = "#ffffff", /* white */
+
+ /* special colors */
+ [256] = "#282a36", /* background */
+ [257] = "#f8f8f2", /* foreground */
+
+ /* more colors can be added after 255 to use with DefaultXX */
+ /* "#cccccc", */
+ /* "#555555", */
+};
+
+
+/*
+ * Default colors (colorname index)
+ * foreground, background, cursor, reverse cursor, selection
+ */
+unsigned int defaultfg = 257;
+unsigned int defaultbg = 256;
+unsigned int defaultcs = 257;
+static unsigned int defaultrcs = 256;
+unsigned int selectionbg = 8;
+unsigned int selectionfg = 257;
+/* If 0 use selectionfg as foreground in order to have a uniform foreground-color */
+/* Else if 1 keep original foreground-color of each cell => more colors :) */
+static int ignoreselfg = 1;
+
+ /*
+ * Colors used, when the specific fg == defaultfg. So in reverse mode this
+ * will reverse too. Another logic would only make the simple feature too
+ * complex.
+ */
+unsigned int defaultitalic = 7;
+unsigned int defaultunderline = 7;
+
+/*
+ * Default shape of cursor
+ * 2: Block ("█")
+ * 4: Underline ("_")
+ * 6: Bar ("|")
+ * 7: Snowman ("☃")
+ */
+static unsigned int cursorshape = 2;
+
+/*
+ * Default columns and rows numbers
+ */
+
+static unsigned int cols = 80;
+static unsigned int rows = 24;
+
+/*
+ * Default colour and shape of the mouse cursor
+ */
+static unsigned int mouseshape = XC_xterm;
+static unsigned int mousefg = 7;
+static unsigned int mousebg = 0;
+
+/*
+ * Color used to display font attributes when fontconfig selected a font which
+ * doesn't match the ones requested.
+ */
+static unsigned int defaultattr = 11;
+
+/*
+ * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set).
+ * Note that if you want to use ShiftMask with selmasks, set this to an other
+ * modifier, set to 0 to not use it.
+ */
+static uint forcemousemod = ShiftMask;
+
+/*
+ * Internal mouse shortcuts.
+ * Beware that overloading Button1 will disable the selection.
+ */
+static MouseShortcut mshortcuts[] = {
+ /* mask button function argument release */
+ /* { XK_ANY_MOD, Button2, clippaste, {.i = 0}, 1 }, */
+ { XK_NO_MOD, Button4, kscrollup, {.i = 1} },
+ { XK_NO_MOD, Button5, kscrolldown, {.i = 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} },
+ { MODKEY, XK_k, kscrollup, {.i = 1} },
+ { MODKEY, XK_j, kscrolldown, {.i = 1} },
+ { MODKEY, XK_u, kscrollup, {.i = -1} },
+ { MODKEY, XK_d, kscrolldown, {.i = -1} },
+ { MODKEY, XK_Page_Up, kscrollup, {.i = -1} },
+ { MODKEY, XK_Page_Down, kscrolldown, {.i = -1} },
+ { TERMMOD, XK_underscore, zoom, {.f = -1} },
+ { TERMMOD, XK_plus, zoom, {.f = +1} },
+ { MODKEY, XK_Home, zoomreset, {.f = 0} },
+ { TERMMOD, XK_C, clipcopy, {.i = 0} },
+ { TERMMOD, XK_V, clippaste, {.i = 0} },
+ { ShiftMask, XK_Insert, clippaste, {.i = 0} },
+ { MODKEY|ShiftMask, XK_Num_Lock, numlock, {.i = 0} },
+ { MODKEY, XK_l, copyurl, {.i = 0} },
+};
+/*
+ * Special keys (change & recompile st.info accordingly)
+ *
+ * Mask value:
+ * * Use XK_ANY_MOD to match the key no matter modifiers state
+ * * Use XK_NO_MOD to match the key alone (no modifiers)
+ * appkey value:
+ * * 0: no value
+ * * > 0: keypad application mode enabled
+ * * = 2: term.numlock = 1
+ * * < 0: keypad application mode disabled
+ * appcursor value:
+ * * 0: no value
+ * * > 0: cursor application mode enabled
+ * * < 0: cursor application mode disabled
+ *
+ * Be careful with the order of the definitions because st searches in
+ * this table sequentially, so any XK_ANY_MOD must be in the last
+ * position for a key.
+ */
+
+/*
+ * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF)
+ * to be mapped below, add them to this array.
+ */
+static KeySym mappedkeys[] = { -1 };
+
+/*
+ * State bits to ignore when matching key or button events. By default,
+ * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored.
+ */
+static uint ignoremod = Mod2Mask|XK_SWITCH_MOD;
+
+/*
+ * This is the huge key array which defines all compatibility to the Linux
+ * world. Please decide about changes wisely.
+ */
+static Key key[] = {
+ /* keysym mask string appkey appcursor */
+ { XK_KP_Home, ShiftMask, "\033[2J", 0, -1},
+ { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1},
+ { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1},
+ { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1},
+ { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0},
+ { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1},
+ { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1},
+ { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0},
+ { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1},
+ { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1},
+ { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0},
+ { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1},
+ { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1},
+ { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0},
+ { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1},
+ { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1},
+ { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0},
+ { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0},
+ { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0},
+ { XK_KP_End, ControlMask, "\033[J", -1, 0},
+ { XK_KP_End, ControlMask, "\033[1;5F", +1, 0},
+ { XK_KP_End, ShiftMask, "\033[K", -1, 0},
+ { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0},
+ { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0},
+ { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0},
+ { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0},
+ { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0},
+ { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0},
+ { XK_KP_Insert, ControlMask, "\033[L", -1, 0},
+ { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0},
+ { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0},
+ { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0},
+ { XK_KP_Delete, ControlMask, "\033[M", -1, 0},
+ { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0},
+ { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0},
+ { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0},
+ { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0},
+ { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0},
+ { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0},
+ { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0},
+ { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0},
+ { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0},
+ { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0},
+ { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0},
+ { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0},
+ { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0},
+ { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0},
+ { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0},
+ { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0},
+ { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0},
+ { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0},
+ { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0},
+ { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0},
+ { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0},
+ { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0},
+ { XK_Up, ShiftMask, "\033[1;2A", 0, 0},
+ { XK_Up, Mod1Mask, "\033[1;3A", 0, 0},
+ { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0},
+ { XK_Up, ControlMask, "\033[1;5A", 0, 0},
+ { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0},
+ { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0},
+ { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0},
+ { XK_Up, XK_ANY_MOD, "\033[A", 0, -1},
+ { XK_Up, XK_ANY_MOD, "\033OA", 0, +1},
+ { XK_Down, ShiftMask, "\033[1;2B", 0, 0},
+ { XK_Down, Mod1Mask, "\033[1;3B", 0, 0},
+ { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0},
+ { XK_Down, ControlMask, "\033[1;5B", 0, 0},
+ { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0},
+ { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0},
+ { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0},
+ { XK_Down, XK_ANY_MOD, "\033[B", 0, -1},
+ { XK_Down, XK_ANY_MOD, "\033OB", 0, +1},
+ { XK_Left, ShiftMask, "\033[1;2D", 0, 0},
+ { XK_Left, Mod1Mask, "\033[1;3D", 0, 0},
+ { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0},
+ { XK_Left, ControlMask, "\033[1;5D", 0, 0},
+ { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0},
+ { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0},
+ { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0},
+ { XK_Left, XK_ANY_MOD, "\033[D", 0, -1},
+ { XK_Left, XK_ANY_MOD, "\033OD", 0, +1},
+ { XK_Right, ShiftMask, "\033[1;2C", 0, 0},
+ { XK_Right, Mod1Mask, "\033[1;3C", 0, 0},
+ { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0},
+ { XK_Right, ControlMask, "\033[1;5C", 0, 0},
+ { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0},
+ { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0},
+ { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0},
+ { XK_Right, XK_ANY_MOD, "\033[C", 0, -1},
+ { XK_Right, XK_ANY_MOD, "\033OC", 0, +1},
+ { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0},
+ { XK_Return, Mod1Mask, "\033\r", 0, 0},
+ { XK_Return, XK_ANY_MOD, "\r", 0, 0},
+ { XK_Insert, ShiftMask, "\033[4l", -1, 0},
+ { XK_Insert, ShiftMask, "\033[2;2~", +1, 0},
+ { XK_Insert, ControlMask, "\033[L", -1, 0},
+ { XK_Insert, ControlMask, "\033[2;5~", +1, 0},
+ { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0},
+ { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0},
+ { XK_Delete, ControlMask, "\033[M", -1, 0},
+ { XK_Delete, ControlMask, "\033[3;5~", +1, 0},
+ { XK_Delete, ShiftMask, "\033[2K", -1, 0},
+ { XK_Delete, ShiftMask, "\033[3;2~", +1, 0},
+ { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0},
+ { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0},
+ { XK_BackSpace, XK_NO_MOD, "\177", 0, 0},
+ { XK_BackSpace, Mod1Mask, "\033\177", 0, 0},
+ { XK_Home, ShiftMask, "\033[2J", 0, -1},
+ { XK_Home, ShiftMask, "\033[1;2H", 0, +1},
+ { XK_Home, XK_ANY_MOD, "\033[H", 0, -1},
+ { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1},
+ { XK_End, ControlMask, "\033[J", -1, 0},
+ { XK_End, ControlMask, "\033[1;5F", +1, 0},
+ { XK_End, ShiftMask, "\033[K", -1, 0},
+ { XK_End, ShiftMask, "\033[1;2F", +1, 0},
+ { XK_End, XK_ANY_MOD, "\033[4~", 0, 0},
+ { XK_Prior, ControlMask, "\033[5;5~", 0, 0},
+ { XK_Prior, ShiftMask, "\033[5;2~", 0, 0},
+ { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0},
+ { XK_Next, ControlMask, "\033[6;5~", 0, 0},
+ { XK_Next, ShiftMask, "\033[6;2~", 0, 0},
+ { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0},
+ { XK_F1, XK_NO_MOD, "\033OP" , 0, 0},
+ { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0},
+ { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0},
+ { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0},
+ { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0},
+ { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0},
+ { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0},
+ { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0},
+ { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0},
+ { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0},
+ { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0},
+ { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0},
+ { XK_F3, XK_NO_MOD, "\033OR" , 0, 0},
+ { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0},
+ { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0},
+ { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0},
+ { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0},
+ { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0},
+ { XK_F4, XK_NO_MOD, "\033OS" , 0, 0},
+ { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0},
+ { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0},
+ { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0},
+ { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0},
+ { XK_F5, XK_NO_MOD, "\033[15~", 0, 0},
+ { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0},
+ { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0},
+ { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0},
+ { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0},
+ { XK_F6, XK_NO_MOD, "\033[17~", 0, 0},
+ { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0},
+ { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0},
+ { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0},
+ { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0},
+ { XK_F7, XK_NO_MOD, "\033[18~", 0, 0},
+ { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0},
+ { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0},
+ { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0},
+ { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0},
+ { XK_F8, XK_NO_MOD, "\033[19~", 0, 0},
+ { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0},
+ { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0},
+ { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0},
+ { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0},
+ { XK_F9, XK_NO_MOD, "\033[20~", 0, 0},
+ { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0},
+ { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0},
+ { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0},
+ { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0},
+ { XK_F10, XK_NO_MOD, "\033[21~", 0, 0},
+ { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0},
+ { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0},
+ { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0},
+ { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0},
+ { XK_F11, XK_NO_MOD, "\033[23~", 0, 0},
+ { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0},
+ { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0},
+ { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0},
+ { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0},
+ { XK_F12, XK_NO_MOD, "\033[24~", 0, 0},
+ { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0},
+ { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0},
+ { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0},
+ { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0},
+ { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0},
+ { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0},
+ { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0},
+ { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0},
+ { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0},
+ { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0},
+ { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0},
+ { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0},
+ { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0},
+ { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0},
+ { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0},
+ { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0},
+ { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0},
+ { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0},
+ { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0},
+ { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0},
+ { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0},
+ { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0},
+ { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0},
+ { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0},
+ { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0},
+ { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0},
+ { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0},
+};
+
+/*
+ * Selection types' masks.
+ * Use the same masks as usual.
+ * Button1Mask is always unset, to make masks match between ButtonPress.
+ * ButtonRelease and MotionNotify.
+ * If no match is found, regular selection is used.
+ */
+static uint selmasks[] = {
+ [SEL_RECTANGULAR] = Mod1Mask,
+};
+
+/*
+ * Printable characters in ASCII, used to estimate the advance width
+ * of single wide characters.
+ */
+static char ascii_printable[] =
+ " !\"#$%&'()*+,-./0123456789:;<=>?"
+ "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+ "`abcdefghijklmnopqrstuvwxyz{|}~";
diff --git a/st/config.mk b/st/config.mk
new file mode 100644
index 0000000..488d80b
--- /dev/null
+++ b/st/config.mk
@@ -0,0 +1,37 @@
+# st version
+VERSION = 0.9
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+TERMINFO = $(PREFIX)/share/terminfo
+
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+
+PKG_CONFIG = pkg-config
+
+# includes and libs
+INCS = -I$(X11INC) \
+ `$(PKG_CONFIG) --cflags fontconfig` \
+ `$(PKG_CONFIG) --cflags freetype2`
+LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\
+ `$(PKG_CONFIG) --libs fontconfig` \
+ `$(PKG_CONFIG) --libs freetype2`
+
+# flags
+CPPFLAGS += -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
+CFLAGS += -std=c99 -pedantic -Wall -O2 $(INCS) $(CPPFLAGS)
+LDFLAGS += $(LIBS)
+
+# 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 = cc
diff --git a/st/plumber b/st/plumber
new file mode 100755
index 0000000..cdac526
--- /dev/null
+++ b/st/plumber
@@ -0,0 +1,222 @@
+#!/bin/sh
+# Copywrong © 2023 Ratakor. Under ISC License.
+# plumber 🪠
+# dependencies:
+# - default programs
+# - xdo
+# - xclip or xsel
+# - (optional) herbe and dmenusearch web
+# - (optional) dmenu_path
+# - (optional) st with plumber patch
+# shellcheck disable=SC2086
+
+# default programs
+WEB="xdg-open"
+TEXT="$TERMINAL -e $EDITOR"
+VIDEO="mpv --ytdl-format=bv+ba --loop"
+AUDIO="music --shuffle"
+PIC="nsxiv -a"
+DOC="zathura"
+DIR="$TERMINAL -e lfub"
+
+HISTDIR=${XDG_STATE_HOME:-$HOME/.local/state}/plumber
+HISTFILE=$HISTDIR/history
+
+openlink() {
+ case "$1" in
+ *.mkv|*.webm|*.mp4|*.mov|*youtube.com/watch*|*youtube.com/playlist*|\
+ *youtube.com/shorts*|*youtu.be*|*inv*/playlist*|*inv*/watch*)
+ setsid -f $VIDEO "$1" >/dev/null 2>&1 ;;
+ *.mp3|*.flac|*.opus|*mp3?source*|*.ogg|*.wav|*soundcloud.com*)
+ setsid -f $AUDIO "$1" >/dev/null 2>&1 ;;
+ *.png|*.jpg|*.jpeg|*.gif|*.webp|*.bmp)
+ file=$(printf '%s' "$1" | sed "s/.*\///;s/%20/ /g")
+ curl -sL "$1" > "/tmp/$file" &&
+ $PIC "/tmp/$file" >/dev/null 2>&1 & ;;
+ *.pdf|*.ps|*.cbz|*.cbr|*.djvu|*.epub)
+ file=$(printf '%s' "$1" | sed "s/.*\///;s/%20/ /g")
+ curl -sL "$1" > "/tmp/$file" &&
+ $DOC "/tmp/$file" >/dev/null 2>&1 & ;;
+ *wp.com/stonetoss.com*)
+ file=$(printf '%s' "${1%"?resize=150%2C150&ssl=1"}" |\
+ sed "s/.*\///;s/%20/ /g")
+ curl -sL "${1%"?resize=150%2C150&ssl=1"}" > "/tmp/$file" &&
+ $PIC "/tmp/$file" >/dev/null 2>&1 & ;;
+ git://*|git@*|*.git)
+ "$TERMINAL" -e git clone "$1" ;;
+ *)
+ setsid -f $WEB "$1" >/dev/null 2>&1 ;;
+ esac
+}
+
+openfile() {
+ if [ -d "$1" ]; then
+ setsid -f $DIR "$1" >/dev/null 2>&1
+ return
+ fi
+
+ if [ ! -f "$1" ]; then
+ printf '\033[31mError:\033[m %s is not a valid file\n' "$1" >&2
+ return 1
+ fi
+
+ case "$1" in
+ *.mkv|*.webm|*.mp4|*.mov)
+ setsid -f $VIDEO "$1" >/dev/null 2>&1 ;;
+ *.mp3|*.flac|*.opus|*.ogg|*.wav)
+ setsid -f $AUDIO "$1" >/dev/null 2>&1 ;;
+ *.png|*.jpg|*.jpeg|*.gif|*.webp|*.bmp)
+ setsid -f $PIC "$1" >/dev/null 2>&1 ;;
+ *.pdf|*.ps|*.cbz|*.cbr|*.djvu|*.epub)
+ setsid -f $DOC "$1" >/dev/null 2>&1 ;;
+ *)
+ setsid -f $TEXT "$1" >/dev/null 2>&1 ;;
+ esac
+}
+
+checksel() {
+ case "$1" in
+ http://*|https://*|irc://*|ircs://*|git://*|git@*)
+ openlink "$1" ;;
+ /*)
+ opensel "$1" "$1" ;;
+
+ *)
+ pid=$(pstree -lpA "$(xdo pid)" | tail -n 1 |\
+ awk -F '---' '{print $NF}' | sed -re 's/[^0-9]//g')
+ opensel "$(readlink "/proc/$pid/cwd")/$1" "$1" ;;
+ esac
+}
+
+opensel() {
+ if [ -d "$1" ]; then
+ case $1 in
+ "${XDG_MUSIC_DIR:-$HOME/Music}"/*)
+ setsid -f $AUDIO "$1" >/dev/null 2>&1 ;;
+ *)
+ xdo kill
+ $TERMINAL -e sh -c "cd $1; $SHELL" ;;
+ esac
+ return
+ fi
+
+ if [ ! -f "$1" ]; then
+ progname=${2%\ *}
+ for prog in $(dmenu_path); do
+ case $prog in
+ "$progname")
+ $TERMINAL -e sh -c\
+ "cd ${1%"$2"}; echo \$ $2; $2; $SHELL"
+ return ;;
+ esac
+ done
+ herbe "Do you want to search for '$2' ?" && dmenusearch web "$2"
+ return
+ fi
+
+ case "$1" in
+ *.mkv|*.webm|*.mp4|*.mov)
+ setsid -f $VIDEO "$1" >/dev/null 2>&1 ;;
+ *.mp3|*.flac|*.opus|*.ogg|*.wav)
+ setsid -f $AUDIO "$1" >/dev/null 2>&1 ;;
+ *.png|*.jpg|*.jpeg|*.gif|*.webp|*.bmp)
+ setsid -f $PIC "$1" >/dev/null 2>&1 ;;
+ *.pdf|*.ps|*.cbz|*.cbr|*.djvu|*.epub)
+ setsid -f $DOC "$1" >/dev/null 2>&1 ;;
+ *)
+ pid=$(xdo pid)
+ xdo hide -p "$pid"
+ $TEXT "$1" >/dev/null 2>&1
+ xdo show -p "$pid" ;;
+ esac
+}
+
+
+usage() {
+ cat << EOF >&2
+Usage: ${0##*/} [options] [args]
+
+Options:
+ no option [args] | Try to guess what to do
+ -l|--link [links] | Open the links
+ -f|--file [files] | Open the files or the folders
+ -s|--sel | Use the selection buffer instead, Plan9's way
+ -t|--term | To be used only when invoking through a patched st
+ -h|--help │ Print this help message
+
+Config:
+ browser = $WEB
+ editor = $TEXT
+ video = $VIDEO
+ audio = $AUDIO
+ picture = $PIC
+ document = $DOC
+ file manager = $DIR
+EOF
+}
+
+main() {
+ mkdir -p "$HISTDIR"
+ case "$1" in
+ -l|--link)
+ shift
+ if [ -z "$1" ]; then
+ printf '\033[31mError:\033[m no argument given\n' >&2
+ usage
+ return 1
+ fi
+ for arg in "$@"; do
+ printf '%s\n' "$arg" >> "$HISTFILE"
+ openlink "$arg"
+ done ;;
+ -f|--file)
+ shift
+ if [ -z "$1" ]; then
+ printf '\033[31mError:\033[m no argument given\n' >&2
+ usage
+ return 1
+ fi
+ for arg in "$@"; do
+ printf '%s\n' "$arg" >> "$HISTFILE"
+ openfile "$arg"
+ done ;;
+ -s|--sel|'')
+ sel=$(xclip -o 2>/dev/null || xsel -opn 2>/dev/null)
+ sel=${sel#"${sel%%[![:space:]]*}"}
+
+ # clear clipboard
+ true | xclip 2>/dev/null || true | xsel 2>/dev/null
+
+ if [ -z "$sel" ]; then
+ printf '\033[31mError:\033[m selection buffer is empty\n' >&2
+ usage
+ return 1
+ fi
+ printf '%s\n' "$sel" >> "$HISTFILE"
+ checksel "$sel" ;;
+ -t|--term)
+ #if [ "$(ps -o comm= $PPID)" != "st" ]; then
+ # printf '\033[31mError:\033[m --term option used outside of st\n' >&2
+ # usage
+ # return 1
+ #fi
+ [ -n "$2" ] && checksel "$2" ;;
+ -h|--help)
+ usage ;;
+ -*)
+ printf '\033[31mError:\033[m unknown option\n' >&2
+ usage
+ return 1 ;;
+ *)
+ for arg in "$@"; do
+ case "$arg" in
+ http://*|https://*|git://*|irc://*|ircs://*)
+ openlink "$arg" ;;
+ *)
+ openfile "$arg" ;;
+ esac
+ done
+ esac
+}
+
+main "$@"
diff --git a/st/st-plumber-0.9.diff b/st/st-plumber-0.9.diff
new file mode 100644
index 0000000..c13327e
--- /dev/null
+++ b/st/st-plumber-0.9.diff
@@ -0,0 +1,36 @@
+From 85588b5d9b832c1c66d745e0ea097bcc3d83964a Mon Sep 17 00:00:00 2001
+From: Ratakor <ratakor@disroot.org>
+Date: Tue, 15 Aug 2023 14:33:12 +0200
+Subject: [PATCH] plumber patch
+
+---
+ x.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/x.c b/x.c
+index aa09997..c9b5ea3 100644
+--- a/x.c
++++ b/x.c
+@@ -471,6 +471,7 @@ bpress(XEvent *e)
+ int btn = e->xbutton.button;
+ struct timespec now;
+ int snap;
++ char *sel;
+
+ if (1 <= btn && btn <= 11)
+ buttons |= 1 << (btn-1);
+@@ -500,6 +501,11 @@ bpress(XEvent *e)
+ xsel.tclick1 = now;
+
+ selstart(evcol(e), evrow(e), snap);
++ if (snap == SNAP_WORD && (sel = getsel())) {
++ if (fork() == 0)
++ execlp("plumber", "plumber", "-t", sel);
++ free(sel);
++ }
+ }
+ }
+
+--
+2.41.0
+
diff --git a/st/st.1 b/st/st.1
new file mode 100644
index 0000000..f222184
--- /dev/null
+++ b/st/st.1
@@ -0,0 +1,185 @@
+.TH ST 1 st\-VERSION
+.SH NAME
+st \- simple terminal
+.SH SYNOPSIS
+.B st
+.RB [ \-aiv ]
+.RB [ \-c
+.IR class ]
+.RB [ \-d
+.IR path ]
+.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 [ \-d
+.IR path ]
+.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 \-d " path"
+changes the working directory to
+.IR path .
+.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 xrdb (1)
+.SH BUGS
+See the TODO file in the distribution.
+
diff --git a/st/st.c b/st/st.c
new file mode 100644
index 0000000..9fc255c
--- /dev/null
+++ b/st/st.c
@@ -0,0 +1,2846 @@
+/* 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;
+
+typedef struct {
+ int state;
+ size_t length;
+} URLdfa;
+
+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 int daddch(URLdfa *, char);
+
+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;
+ case 't': /* title stack operations */
+ switch (csiescseq.arg[0]) {
+ case 22: /* pust current title on stack */
+ switch (csiescseq.arg[1]) {
+ case 0:
+ case 1:
+ case 2:
+ xpushtitle();
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 23: /* pop last title from stack */
+ switch (csiescseq.arg[1]) {
+ case 0:
+ case 1:
+ case 2:
+ xsettitle(NULL, 1);
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ default:
+ goto unknown;
+ }
+ }
+}
+
+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], 0);
+ xseticontitle(strescseq.args[1]);
+ }
+ return;
+ case 1:
+ if (narg > 1)
+ xseticontitle(strescseq.args[1]);
+ return;
+ case 2:
+ if (narg > 1)
+ xsettitle(strescseq.args[1], 0);
+ 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], 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();
+ xfreetitlestack();
+ 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, 0);
+}
+
+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();
+}
+
+int
+daddch(URLdfa *dfa, char c)
+{
+ /* () and [] can appear in urls, but excluding them here will reduce false
+ * positives when figuring out where a given url ends.
+ */
+ static const char URLCHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789-._~:/?#@!$&'*+,;=%";
+ static const char RPFX[] = "//:sptth";
+
+ if (!strchr(URLCHARS, c)) {
+ dfa->length = 0;
+ dfa->state = 0;
+
+ return 0;
+ }
+
+ dfa->length++;
+
+ if (dfa->state == 2 && c == '/') {
+ dfa->state = 0;
+ } else if (dfa->state == 3 && c == 'p') {
+ dfa->state++;
+ } else if (c != RPFX[dfa->state]) {
+ dfa->state = 0;
+ return 0;
+ }
+
+ if (dfa->state++ == 7) {
+ dfa->state = 0;
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+** Select and copy the previous url on screen (do nothing if there's no url).
+*/
+void
+copyurl(const Arg *arg) {
+ int row = 0, /* row of current URL */
+ col = 0, /* column of current URL start */
+ colend = 0, /* column of last occurrence */
+ passes = 0; /* how many rows have been scanned */
+
+ URLdfa dfa = { 0 };
+
+ row = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.y : term.bot;
+ LIMIT(row, term.top, term.bot);
+
+ colend = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.x : term.col;
+ LIMIT(colend, 0, term.col);
+
+ /*
+ ** Scan from (term.row - 1,term.col - 1) to (0,0) and find
+ ** next occurrance of a URL
+ */
+ for (passes = 0; passes < term.row; passes++) {
+ /* Read in each column of every row until
+ ** we hit previous occurrence of URL
+ */
+ for (col = colend; col--;)
+ if (daddch(&dfa, term.line[row][col].u < 128 ? term.line[row][col].u : ' '))
+ break;
+
+ if (col >= 0)
+ break;
+
+ if (--row < 0)
+ row = term.row - 1;
+
+ colend = term.col;
+ }
+
+ if (passes < term.row) {
+ selstart(col, row, 0);
+ selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 0);
+ selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 1);
+ xsetsel(getsel());
+ xclipcopy();
+ }
+}
diff --git a/st/st.h b/st/st.h
new file mode 100644
index 0000000..68e8bf5
--- /dev/null
+++ b/st/st.h
@@ -0,0 +1,131 @@
+/* See LICENSE for license details. */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+/* macros */
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) < (b) ? (b) : (a))
+#define LEN(a) (sizeof(a) / sizeof(a)[0])
+#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
+#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
+#define DEFAULT(a, b) (a) = (a) ? (a) : (b)
+#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
+#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
+ (a).bg != (b).bg)
+#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
+ (t1.tv_nsec-t2.tv_nsec)/1E6)
+#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
+
+#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
+#define IS_TRUECOL(x) (1 << 24 & (x))
+
+enum glyph_attribute {
+ ATTR_NULL = 0,
+ ATTR_BOLD = 1 << 0,
+ ATTR_FAINT = 1 << 1,
+ ATTR_ITALIC = 1 << 2,
+ ATTR_UNDERLINE = 1 << 3,
+ ATTR_BLINK = 1 << 4,
+ ATTR_REVERSE = 1 << 5,
+ ATTR_INVISIBLE = 1 << 6,
+ ATTR_STRUCK = 1 << 7,
+ ATTR_WRAP = 1 << 8,
+ ATTR_WIDE = 1 << 9,
+ ATTR_WDUMMY = 1 << 10,
+ ATTR_SELECTED = 1 << 11,
+ 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 *);
+void copyurl(const Arg *);
+
+int tattrset(int);
+void tnew(int, int);
+void tresize(int, int);
+void tsetdirtattr(int);
+void ttyhangup(void);
+int ttynew(const char *, char *, const char *, char **);
+size_t ttyread(void);
+void ttyresize(int, int);
+void ttywrite(const char *, size_t, int);
+
+void resettitle(void);
+
+void selclear(void);
+void selinit(void);
+void selstart(int, int, int);
+void selextend(int, int, int, int);
+int selected(int, int);
+char *getsel(void);
+
+size_t utf8encode(Rune, char *);
+
+void *xmalloc(size_t);
+void *xrealloc(void *, size_t);
+char *xstrdup(const char *);
+
+/* config.h globals */
+extern char *utmp;
+extern char *scroll;
+extern char *stty_args;
+extern char *vtiden;
+extern wchar_t *worddelimiters;
+extern int allowaltscreen;
+extern int allowwindowops;
+extern char *termname;
+extern unsigned int tabspaces;
+extern unsigned int defaultfg;
+extern unsigned int defaultbg;
+extern unsigned int defaultcs;
+extern float alpha;
diff --git a/st/st.info b/st/st.info
new file mode 100644
index 0000000..aeef606
--- /dev/null
+++ b/st/st.info
@@ -0,0 +1,239 @@
+st-mono| simpleterm monocolor,
+ acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
+ am,
+ bce,
+ bel=^G,
+ blink=\E[5m,
+ bold=\E[1m,
+ cbt=\E[Z,
+ cvvis=\E[?25h,
+ civis=\E[?25l,
+ clear=\E[H\E[2J,
+ cnorm=\E[?12l\E[?25h,
+ colors#2,
+ cols#80,
+ cr=^M,
+ csr=\E[%i%p1%d;%p2%dr,
+ cub=\E[%p1%dD,
+ cub1=^H,
+ cud1=^J,
+ cud=\E[%p1%dB,
+ cuf1=\E[C,
+ cuf=\E[%p1%dC,
+ cup=\E[%i%p1%d;%p2%dH,
+ cuu1=\E[A,
+ cuu=\E[%p1%dA,
+ dch=\E[%p1%dP,
+ dch1=\E[P,
+ dim=\E[2m,
+ dl=\E[%p1%dM,
+ dl1=\E[M,
+ ech=\E[%p1%dX,
+ ed=\E[J,
+ el=\E[K,
+ el1=\E[1K,
+ enacs=\E)0,
+ flash=\E[?5h$<80/>\E[?5l,
+ fsl=^G,
+ home=\E[H,
+ hpa=\E[%i%p1%dG,
+ hs,
+ ht=^I,
+ hts=\EH,
+ ich=\E[%p1%d@,
+ il1=\E[L,
+ il=\E[%p1%dL,
+ ind=^J,
+ indn=\E[%p1%dS,
+ invis=\E[8m,
+ is2=\E[4l\E>\E[?1034l,
+ it#8,
+ kel=\E[1;2F,
+ ked=\E[1;5F,
+ ka1=\E[1~,
+ ka3=\E[5~,
+ kc1=\E[4~,
+ kc3=\E[6~,
+ kbs=\177,
+ kcbt=\E[Z,
+ kb2=\EOu,
+ kcub1=\EOD,
+ kcud1=\EOB,
+ kcuf1=\EOC,
+ kcuu1=\EOA,
+ kDC=\E[3;2~,
+ kent=\EOM,
+ kEND=\E[1;2F,
+ kIC=\E[2;2~,
+ kNXT=\E[6;2~,
+ kPRV=\E[5;2~,
+ kHOM=\E[1;2H,
+ kLFT=\E[1;2D,
+ kRIT=\E[1;2C,
+ kind=\E[1;2B,
+ kri=\E[1;2A,
+ kclr=\E[3;5~,
+ kdl1=\E[3;2~,
+ kdch1=\E[3~,
+ kich1=\E[2~,
+ kend=\E[4~,
+ kf1=\EOP,
+ kf2=\EOQ,
+ kf3=\EOR,
+ kf4=\EOS,
+ kf5=\E[15~,
+ kf6=\E[17~,
+ kf7=\E[18~,
+ kf8=\E[19~,
+ kf9=\E[20~,
+ kf10=\E[21~,
+ kf11=\E[23~,
+ kf12=\E[24~,
+ kf13=\E[1;2P,
+ kf14=\E[1;2Q,
+ kf15=\E[1;2R,
+ kf16=\E[1;2S,
+ kf17=\E[15;2~,
+ kf18=\E[17;2~,
+ kf19=\E[18;2~,
+ kf20=\E[19;2~,
+ kf21=\E[20;2~,
+ kf22=\E[21;2~,
+ kf23=\E[23;2~,
+ kf24=\E[24;2~,
+ kf25=\E[1;5P,
+ kf26=\E[1;5Q,
+ kf27=\E[1;5R,
+ kf28=\E[1;5S,
+ kf29=\E[15;5~,
+ kf30=\E[17;5~,
+ kf31=\E[18;5~,
+ kf32=\E[19;5~,
+ kf33=\E[20;5~,
+ kf34=\E[21;5~,
+ kf35=\E[23;5~,
+ kf36=\E[24;5~,
+ kf37=\E[1;6P,
+ kf38=\E[1;6Q,
+ kf39=\E[1;6R,
+ kf40=\E[1;6S,
+ kf41=\E[15;6~,
+ kf42=\E[17;6~,
+ kf43=\E[18;6~,
+ kf44=\E[19;6~,
+ kf45=\E[20;6~,
+ kf46=\E[21;6~,
+ kf47=\E[23;6~,
+ kf48=\E[24;6~,
+ kf49=\E[1;3P,
+ kf50=\E[1;3Q,
+ kf51=\E[1;3R,
+ kf52=\E[1;3S,
+ kf53=\E[15;3~,
+ kf54=\E[17;3~,
+ kf55=\E[18;3~,
+ kf56=\E[19;3~,
+ kf57=\E[20;3~,
+ kf58=\E[21;3~,
+ kf59=\E[23;3~,
+ kf60=\E[24;3~,
+ kf61=\E[1;4P,
+ kf62=\E[1;4Q,
+ kf63=\E[1;4R,
+ khome=\E[1~,
+ kil1=\E[2;5~,
+ krmir=\E[2;2~,
+ knp=\E[6~,
+ kmous=\E[M,
+ kpp=\E[5~,
+ lines#24,
+ mir,
+ msgr,
+ npc,
+ op=\E[39;49m,
+ pairs#64,
+ mc0=\E[i,
+ mc4=\E[4i,
+ mc5=\E[5i,
+ rc=\E8,
+ rev=\E[7m,
+ ri=\EM,
+ rin=\E[%p1%dT,
+ ritm=\E[23m,
+ rmacs=\E(B,
+ rmcup=\E[?1049l\E[23;0;0t,
+ 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\E[22;0;0t,
+ smir=\E[4h,
+ smkx=\E[?1h\E=,
+ smso=\E[7m,
+ smul=\E[4m,
+ tbc=\E[3g,
+ tsl=\E]0;,
+ xenl,
+ vpa=\E[%i%p1%dd,
+# XTerm extensions
+ rmxx=\E[29m,
+ smxx=\E[9m,
+# disabled rep for now: causes some issues with older ncurses versions.
+# rep=%p1%c\E[%p2%{1}%-%db,
+# tmux extensions, see TERMINFO EXTENSIONS in tmux(1)
+ Tc,
+ Ms=\E]52;%p1%s;%p2%s\007,
+ Se=\E[2 q,
+ Ss=\E[%p1%d q,
+
+st| simpleterm,
+ use=st-mono,
+ colors#8,
+ setab=\E[4%p1%dm,
+ setaf=\E[3%p1%dm,
+ setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
+ setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
+ sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m,
+
+st-256color| simpleterm with 256 colors,
+ use=st,
+ ccc,
+ colors#256,
+ oc=\E]104\007,
+ pairs#32767,
+# Nicked from xterm-256color
+ initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\,
+ setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
+ setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,
+
+st-meta| simpleterm with meta key,
+ use=st,
+ km,
+ rmm=\E[?1034l,
+ smm=\E[?1034h,
+ rs2=\E[4l\E>\E[?1034h,
+ is2=\E[4l\E>\E[?1034h,
+
+st-meta-256color| simpleterm with meta key and 256 colors,
+ use=st-256color,
+ km,
+ rmm=\E[?1034l,
+ smm=\E[?1034h,
+ rs2=\E[4l\E>\E[?1034h,
+ is2=\E[4l\E>\E[?1034h,
+
+st-bs| simpleterm with backspace as backspace,
+ use=st,
+ kbs=\010,
+ kdch1=\177,
+
+st-bs-256color| simpleterm with backspace as backspace and 256colors,
+ use=st-256color,
+ kbs=\010,
+ kdch1=\177,
diff --git a/st/win.h b/st/win.h
new file mode 100644
index 0000000..2a40aa0
--- /dev/null
+++ b/st/win.h
@@ -0,0 +1,43 @@
+/* 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 xfreetitlestack(void);
+void xsettitle(char *, int);
+void xpushtitle(void);
+int xsetcursor(int);
+void xsetmode(int, unsigned int);
+void xsetpointermotion(int);
+void xsetsel(char *);
+int xstartdraw(void);
+void xximspot(int, int);
diff --git a/st/x.c b/st/x.c
new file mode 100644
index 0000000..bd2d716
--- /dev/null
+++ b/st/x.c
@@ -0,0 +1,2391 @@
+/* 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>
+#include <X11/Xresource.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"
+
+/* size of title stack */
+#define TITLESTACKSIZE 8
+
+/* 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 hborderpx, vborderpx;
+ int ch; /* char height */
+ int cw; /* char width */
+ int mode; /* window state/mode flags */
+ int cursor; /* cursor style */
+} TermWindow;
+
+typedef struct {
+ Display *dpy;
+ Colormap cmap;
+ Window win;
+ Drawable buf;
+ GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
+ Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
+ struct {
+ XIM xim;
+ XIC xic;
+ XPoint spot;
+ XVaNestedList spotlist;
+ } ime;
+ Draw draw;
+ Visual *vis;
+ XSetWindowAttributes attrs;
+ int scr;
+ int isfixed; /* is fixed geometry? */
+ int depth; /* bit depth */
+ int l, t; /* left and top offset */
+ int gm; /* geometry mask */
+} XWindow;
+
+typedef struct {
+ Atom xtarget;
+ char *primary, *clipboard;
+ struct timespec tclick1;
+ struct timespec tclick2;
+} XSelection;
+
+/* Font structure */
+#define Font Font_
+typedef struct {
+ int height;
+ int width;
+ int ascent;
+ int descent;
+ int badslant;
+ int badweight;
+ short lbearing;
+ short rbearing;
+ XftFont *match;
+ FcFontSet *set;
+ FcPattern *pattern;
+} Font;
+
+/* Drawing Context */
+typedef struct {
+ Color *col;
+ size_t collen;
+ Font font, bfont, ifont, ibfont;
+ GC gc;
+} DC;
+
+static inline ushort sixd_to_16bit(int);
+static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
+static void xdrawglyph(Glyph, int, int);
+static void xclear(int, int, int, int);
+static int xgeommasktogravity(int);
+static int ximopen(Display *);
+static void ximinstantiate(Display *, XPointer, XPointer);
+static void ximdestroy(XIM, XPointer, XPointer);
+static int xicdestroy(XIC, XPointer, XPointer);
+static void xinit(int, int);
+static void cresize(int, int);
+static void xresize(int, int);
+static void xhints(void);
+static int xloadcolor(int, const char *, Color *);
+static int xloadfont(Font *, FcPattern *);
+static void xloadfonts(const char *, double);
+static int xloadsparefont(FcPattern *, int);
+static void xloadsparefonts(void);
+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;
+static int tstki; /* title stack index */
+static char *titlestack[TITLESTACKSIZE]; /* title stack */
+
+/* Font Ring Cache */
+enum {
+ FRC_NORMAL,
+ FRC_ITALIC,
+ FRC_BOLD,
+ FRC_ITALICBOLD
+};
+
+typedef struct {
+ XftFont *font;
+ int flags;
+ Rune unicodep;
+} Fontcache;
+
+/* Fontcache is an array now. A new font will be appended to the array. */
+static Fontcache *frc = NULL;
+static int frclen = 0;
+static int frccap = 0;
+static char *usedfont = NULL;
+static double usedfontsize = 0;
+static double defaultfontsize = 0;
+
+static char *opt_alpha = NULL;
+static char *opt_class = NULL;
+static char **opt_cmd = NULL;
+static char *opt_embed = NULL;
+static char *opt_font = NULL;
+static char *opt_io = NULL;
+static char *opt_line = NULL;
+static char *opt_name = NULL;
+static char *opt_title = NULL;
+static char *opt_dir = 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);
+ xloadsparefonts();
+ 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 - win.hborderpx;
+ LIMIT(x, 0, win.tw - 1);
+ return x / win.cw;
+}
+
+int
+evrow(XEvent *e)
+{
+ int y = e->xbutton.y - win.vborderpx;
+ 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;
+ char *sel;
+
+ 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);
+ if (snap == SNAP_WORD && (sel = getsel())) {
+ if (fork() == 0)
+ execlp("plumber", "plumber", "-t", sel);
+ free(sel);
+ }
+ }
+}
+
+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();
+ clipcopy(NULL);
+}
+
+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);
+
+ win.hborderpx = (win.w - col * win.cw) / 2;
+ win.vborderpx = (win.h - row * win.ch) / 2;
+
+ tresize(col, row);
+ xresize(col, row);
+ ttyresize(win.tw, win.th);
+}
+
+void
+xresize(int col, int row)
+{
+ win.tw = col * win.cw;
+ win.th = row * win.ch;
+
+ XFreePixmap(xw.dpy, xw.buf);
+ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
+ xw.depth);
+ XftDrawChange(xw.draw, xw.buf);
+ xclear(0, 0, win.w, win.h);
+
+ /* resize to new width */
+ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
+}
+
+ushort
+sixd_to_16bit(int x)
+{
+ return x == 0 ? 0 : 0x3737 + 0x2828 * x;
+}
+
+int
+xloadcolor(int i, const char *name, Color *ncolor)
+{
+ XRenderColor color = { .alpha = 0xffff };
+
+ if (!name) {
+ if (BETWEEN(i, 16, 255)) { /* 256 color */
+ if (i < 6*6*6+16) { /* same colors as xterm */
+ color.red = sixd_to_16bit( ((i-16)/36)%6 );
+ color.green = sixd_to_16bit( ((i-16)/6) %6 );
+ color.blue = sixd_to_16bit( ((i-16)/1) %6 );
+ } else { /* greyscale */
+ color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));
+ color.green = color.blue = color.red;
+ }
+ return XftColorAllocValue(xw.dpy, xw.vis,
+ xw.cmap, &color, ncolor);
+ } else
+ name = colorname[i];
+ }
+
+ return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
+}
+
+void
+xloadcols(void)
+{
+ int i;
+ static int loaded;
+ Color *cp;
+
+ if (loaded) {
+ for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
+ XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
+ } else {
+ dc.collen = MAX(LEN(colorname), 256);
+ dc.col = xmalloc(dc.collen * sizeof(Color));
+ }
+
+ for (i = 0; i < dc.collen; i++)
+ if (!xloadcolor(i, NULL, &dc.col[i])) {
+ if (colorname[i])
+ die("could not allocate color '%s'\n", colorname[i]);
+ else
+ die("could not allocate color %d\n", i);
+ }
+
+ /* set alpha value of bg color */
+ if (opt_alpha)
+ alpha = strtof(opt_alpha, NULL);
+ dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha);
+ dc.col[defaultbg].pixel &= 0x00FFFFFF;
+ dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24;
+
+ /* set alpha value of selbg color */
+ dc.col[selectionbg].color.alpha = (unsigned short)(0xffff * alpha);
+ dc.col[selectionbg].color.red =
+ ((unsigned short)(dc.col[selectionbg].color.red * alpha)) & 0xff00;
+ dc.col[selectionbg].color.green =
+ ((unsigned short)(dc.col[selectionbg].color.green * alpha)) & 0xff00;
+ dc.col[selectionbg].color.blue =
+ ((unsigned short)(dc.col[selectionbg].color.blue * alpha)) & 0xff00;
+ dc.col[selectionbg].pixel &= 0x00FFFFFF;
+ dc.col[selectionbg].pixel |= (unsigned char)(0xff * alpha) << 24;
+ loaded = 1;
+}
+
+int
+xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b)
+{
+ if (!BETWEEN(x, 0, dc.collen))
+ return 1;
+
+ *r = dc.col[x].color.red >> 8;
+ *g = dc.col[x].color.green >> 8;
+ *b = dc.col[x].color.blue >> 8;
+
+ return 0;
+}
+
+int
+xsetcolorname(int x, const char *name)
+{
+ Color ncolor;
+
+ if (!BETWEEN(x, 0, dc.collen))
+ return 1;
+
+ if (!xloadcolor(x, name, &ncolor))
+ return 1;
+
+ XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
+ dc.col[x] = ncolor;
+
+ return 0;
+}
+
+/*
+ * Absolute coordinates.
+ */
+void
+xclear(int x1, int y1, int x2, int y2)
+{
+ XftDrawRect(xw.draw,
+ &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
+ x1, y1, x2-x1, y2-y1);
+}
+
+void
+xhints(void)
+{
+ XClassHint class = {opt_name ? opt_name : termname,
+ opt_class ? opt_class : termname};
+ XWMHints wm = {.flags = InputHint, .input = 1};
+ XSizeHints *sizeh;
+
+ sizeh = XAllocSizeHints();
+
+ sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
+ sizeh->height = win.h;
+ sizeh->width = win.w;
+ sizeh->height_inc = 1;
+ sizeh->width_inc = 1;
+ 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);
+}
+
+int
+xloadsparefont(FcPattern *pattern, int flags)
+{
+ FcPattern *match;
+ FcResult result;
+
+ match = FcFontMatch(NULL, pattern, &result);
+ if (!match) {
+ return 1;
+ }
+
+ if (!(frc[frclen].font = XftFontOpenPattern(xw.dpy, match))) {
+ FcPatternDestroy(match);
+ return 1;
+ }
+
+ frc[frclen].flags = flags;
+ /* Believe U+0000 glyph will present in each default font */
+ frc[frclen].unicodep = 0;
+ frclen++;
+
+ return 0;
+}
+
+void
+xloadsparefonts(void)
+{
+ FcPattern *pattern;
+ double sizeshift, fontval;
+ int fc;
+ char **fp;
+
+ if (frclen != 0)
+ die("can't embed spare fonts. cache isn't empty");
+
+ /* Calculate count of spare fonts */
+ fc = sizeof(font2) / sizeof(*font2);
+ if (fc == 0)
+ return;
+
+ /* Allocate memory for cache entries. */
+ if (frccap < 4 * fc) {
+ frccap += 4 * fc - frccap;
+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
+ }
+
+ for (fp = font2; fp - font2 < fc; ++fp) {
+
+ if (**fp == '-')
+ pattern = XftXlfdParse(*fp, False, False);
+ else
+ pattern = FcNameParse((FcChar8 *)*fp);
+
+ if (!pattern)
+ die("can't open spare font %s\n", *fp);
+
+ if (defaultfontsize > 0) {
+ sizeshift = usedfontsize - defaultfontsize;
+ if (sizeshift != 0 &&
+ FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
+ FcResultMatch) {
+ fontval += sizeshift;
+ FcPatternDel(pattern, FC_PIXEL_SIZE);
+ FcPatternDel(pattern, FC_SIZE);
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontval);
+ }
+ }
+
+ FcPatternAddBool(pattern, FC_SCALABLE, 1);
+
+ FcConfigSubstitute(NULL, pattern, FcMatchPattern);
+ XftDefaultSubstitute(xw.dpy, xw.scr, pattern);
+
+ if (xloadsparefont(pattern, FRC_NORMAL))
+ die("can't open spare font %s\n", *fp);
+
+ FcPatternDel(pattern, FC_SLANT);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
+ if (xloadsparefont(pattern, FRC_ITALIC))
+ die("can't open spare font %s\n", *fp);
+
+ FcPatternDel(pattern, FC_WEIGHT);
+ FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
+ if (xloadsparefont(pattern, FRC_ITALICBOLD))
+ die("can't open spare font %s\n", *fp);
+
+ FcPatternDel(pattern, FC_SLANT);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
+ if (xloadsparefont(pattern, FRC_BOLD))
+ die("can't open spare font %s\n", *fp);
+
+ FcPatternDestroy(pattern);
+ }
+}
+
+void
+xunloadfont(Font *f)
+{
+ XftFontClose(xw.dpy, f->match);
+ FcPatternDestroy(f->pattern);
+ if (f->set)
+ FcFontSetDestroy(f->set);
+}
+
+void
+xunloadfonts(void)
+{
+ /* Free the loaded fonts in the font cache. */
+ while (frclen > 0)
+ XftFontClose(xw.dpy, frc[--frclen].font);
+
+ xunloadfont(&dc.font);
+ xunloadfont(&dc.bfont);
+ xunloadfont(&dc.ifont);
+ xunloadfont(&dc.ibfont);
+}
+
+int
+ximopen(Display *dpy)
+{
+ XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
+ XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
+
+ xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
+ if (xw.ime.xim == NULL)
+ return 0;
+
+ if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL))
+ fprintf(stderr, "XSetIMValues: "
+ "Could not set XNDestroyCallback.\n");
+
+ xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot,
+ NULL);
+
+ if (xw.ime.xic == NULL) {
+ xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,
+ XIMPreeditNothing | XIMStatusNothing,
+ XNClientWindow, xw.win,
+ XNDestroyCallback, &icdestroy,
+ NULL);
+ }
+ if (xw.ime.xic == NULL)
+ fprintf(stderr, "XCreateIC: Could not create input context.\n");
+
+ return 1;
+}
+
+void
+ximinstantiate(Display *dpy, XPointer client, XPointer call)
+{
+ if (ximopen(dpy))
+ XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+ ximinstantiate, NULL);
+}
+
+void
+ximdestroy(XIM xim, XPointer client, XPointer call)
+{
+ xw.ime.xim = NULL;
+ XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+ ximinstantiate, NULL);
+ XFree(xw.ime.spotlist);
+}
+
+int
+xicdestroy(XIC xim, XPointer client, XPointer call)
+{
+ xw.ime.xic = NULL;
+ return 1;
+}
+
+void
+xinit(int cols, int rows)
+{
+ XGCValues gcvalues;
+ Cursor cursor;
+ Window parent;
+ pid_t thispid = getpid();
+ XColor xmousefg, xmousebg;
+ XWindowAttributes attr;
+ XVisualInfo vis;
+
+ if (!(xw.dpy = XOpenDisplay(NULL)))
+ die("can't open display\n");
+
+ xw.scr = XDefaultScreen(xw.dpy);
+
+ if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) {
+ parent = XRootWindow(xw.dpy, xw.scr);
+ xw.depth = 32;
+ } else {
+ XGetWindowAttributes(xw.dpy, parent, &attr);
+ xw.depth = attr.depth;
+ }
+
+ XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis);
+ xw.vis = vis.visual;
+
+ /* font */
+ if (!FcInit())
+ die("could not init fontconfig.\n");
+
+ usedfont = (opt_font == NULL)? font : opt_font;
+ xloadfonts(usedfont, 0);
+
+ /* spare fonts */
+ xloadsparefonts();
+
+ /* colors */
+ xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None);
+ xloadcols();
+
+ /* adjust fixed window geometry */
+ win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw;
+ win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch;
+ if (xw.gm & XNegative)
+ xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
+ if (xw.gm & YNegative)
+ xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2;
+
+ /* Events */
+ xw.attrs.background_pixel = dc.col[defaultbg].pixel;
+ xw.attrs.border_pixel = dc.col[defaultbg].pixel;
+ xw.attrs.bit_gravity = NorthWestGravity;
+ xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask
+ | ExposureMask | VisibilityChangeMask | StructureNotifyMask
+ | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
+ xw.attrs.colormap = xw.cmap;
+
+ xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
+ win.w, win.h, 0, xw.depth, InputOutput,
+ xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
+ | CWEventMask | CWColormap, &xw.attrs);
+
+ memset(&gcvalues, 0, sizeof(gcvalues));
+ gcvalues.graphics_exposures = False;
+ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth);
+ dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues);
+ XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
+ XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
+
+ /* font spec buffer */
+ xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
+
+ /* Xft rendering context */
+ xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
+
+ /* input methods */
+ if (!ximopen(xw.dpy)) {
+ XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+ ximinstantiate, NULL);
+ }
+
+ /* white cursor, black outline */
+ cursor = XCreateFontCursor(xw.dpy, mouseshape);
+ XDefineCursor(xw.dpy, xw.win, cursor);
+
+ if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) {
+ xmousefg.red = 0xffff;
+ xmousefg.green = 0xffff;
+ xmousefg.blue = 0xffff;
+ }
+
+ if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) {
+ xmousebg.red = 0x0000;
+ xmousebg.green = 0x0000;
+ xmousebg.blue = 0x0000;
+ }
+
+ XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);
+
+ xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
+ xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
+ xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
+ xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);
+ XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
+
+ xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
+ XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
+ PropModeReplace, (uchar *)&thispid, 1);
+
+ win.mode = MODE_NUMLOCK;
+ resettitle();
+ xhints();
+ XMapWindow(xw.dpy, xw.win);
+ XSync(xw.dpy, False);
+
+ clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);
+ clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2);
+ xsel.primary = NULL;
+ xsel.clipboard = NULL;
+ xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
+ if (xsel.xtarget == None)
+ xsel.xtarget = XA_STRING;
+}
+
+int
+xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
+{
+ float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + 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 = win.hborderpx + x * win.cw, winy = win.vborderpx + 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];
+ }
+
+ 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_SELECTED) {
+ bg = &dc.col[selectionbg];
+ if (!ignoreselfg)
+ fg = &dc.col[selectionfg];
+ }
+
+ 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, win.hborderpx,
+ winy + win.ch +
+ ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0));
+ }
+ if (winx + width >= win.hborderpx + win.tw) {
+ xclear(winx + width, (y == 0)? 0 : winy, win.w,
+ ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch)));
+ }
+ if (y == 0)
+ xclear(winx, 0, winx + width, win.vborderpx);
+ if (winy + win.ch >= win.vborderpx + 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_SELECTED;
+ 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.fg = defaultcs;
+ g.bg = defaultfg;
+ drawcol = dc.col[defaultrcs];
+ } else {
+ g.fg = defaultbg;
+ g.bg = defaultcs;
+ drawcol = dc.col[defaultcs];
+ }
+
+ /* 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,
+ win.hborderpx + cx * win.cw,
+ win.vborderpx + (cy + 1) * win.ch - \
+ cursorthickness,
+ win.cw, cursorthickness);
+ break;
+ case 5: /* Blinking bar */
+ case 6: /* Steady bar */
+ XftDrawRect(xw.draw, &drawcol,
+ win.hborderpx + cx * win.cw,
+ win.vborderpx + cy * win.ch,
+ cursorthickness, win.ch);
+ break;
+ }
+ } else {
+ XftDrawRect(xw.draw, &drawcol,
+ win.hborderpx + cx * win.cw,
+ win.vborderpx + cy * win.ch,
+ win.cw - 1, 1);
+ XftDrawRect(xw.draw, &drawcol,
+ win.hborderpx + cx * win.cw,
+ win.vborderpx + cy * win.ch,
+ 1, win.ch - 1);
+ XftDrawRect(xw.draw, &drawcol,
+ win.hborderpx + (cx + 1) * win.cw - 1,
+ win.vborderpx + cy * win.ch,
+ 1, win.ch - 1);
+ XftDrawRect(xw.draw, &drawcol,
+ win.hborderpx + cx * win.cw,
+ win.vborderpx + (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
+xfreetitlestack(void)
+{
+ for (int i = 0; i < LEN(titlestack); i++) {
+ free(titlestack[i]);
+ titlestack[i] = NULL;
+ }
+}
+
+void
+xsettitle(char *p, int pop)
+{
+ XTextProperty prop;
+
+ free(titlestack[tstki]);
+ if (pop) {
+ titlestack[tstki] = NULL;
+ tstki = (tstki - 1 + TITLESTACKSIZE) % TITLESTACKSIZE;
+ p = titlestack[tstki] ? titlestack[tstki] : opt_title;
+ } else if (p) {
+ titlestack[tstki] = xstrdup(p);
+ } else {
+ titlestack[tstki] = NULL;
+ 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);
+}
+
+void
+xpushtitle(void)
+{
+ int tstkin = (tstki + 1) % TITLESTACKSIZE;
+
+ free(titlestack[tstkin]);
+ titlestack[tstkin] = titlestack[tstki] ? xstrdup(titlestack[tstki]) : NULL;
+ tstki = tstkin;
+}
+
+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_SELECTED;
+ 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;
+ }
+}
+
+
+#define XRESOURCE_LOAD_META(NAME) \
+ if(!XrmGetResource(xrdb, "st." NAME, "st." NAME, &type, &ret)) \
+ XrmGetResource(xrdb, "*." NAME, "*." NAME, &type, &ret); \
+ if (ret.addr != NULL && !strncmp("String", type, 64))
+
+#define XRESOURCE_LOAD_STRING(NAME, DST) \
+ XRESOURCE_LOAD_META(NAME) \
+ DST = ret.addr;
+
+#define XRESOURCE_LOAD_CHAR(NAME, DST) \
+ XRESOURCE_LOAD_META(NAME) \
+ DST = ret.addr[0];
+
+#define XRESOURCE_LOAD_INTEGER(NAME, DST) \
+ XRESOURCE_LOAD_META(NAME) \
+ DST = strtoul(ret.addr, NULL, 10);
+
+#define XRESOURCE_LOAD_FLOAT(NAME, DST) \
+ XRESOURCE_LOAD_META(NAME) \
+ DST = strtof(ret.addr, NULL);
+
+void
+xrdb_load(void)
+{
+ /* XXX */
+ char *xrm;
+ char *type;
+ XrmDatabase xrdb;
+ XrmValue ret;
+ Display *dpy;
+
+ if(!(dpy = XOpenDisplay(NULL)))
+ die("Can't open display\n");
+
+ XrmInitialize();
+ xrm = XResourceManagerString(dpy);
+
+ if (xrm != NULL) {
+ xrdb = XrmGetStringDatabase(xrm);
+
+ /* handling colors here without macros to do via loop. */
+ int i = 0;
+ char loadValue[12] = "";
+ for (i = 0; i < 256; i++)
+ {
+ sprintf(loadValue, "%s%d", "st.color", i);
+
+ if(!XrmGetResource(xrdb, loadValue, loadValue, &type, &ret))
+ {
+ sprintf(loadValue, "%s%d", "*.color", i);
+ if (!XrmGetResource(xrdb, loadValue, loadValue, &type, &ret))
+ /* reset if not found (unless in range for defaults). */
+ if (i > 15)
+ colorname[i] = NULL;
+ }
+
+ if (ret.addr != NULL && !strncmp("String", type, 64))
+ colorname[i] = ret.addr;
+ }
+
+ XRESOURCE_LOAD_STRING("foreground", colorname[defaultfg]);
+ XRESOURCE_LOAD_STRING("background", colorname[defaultbg]);
+ XRESOURCE_LOAD_STRING("cursorColor", colorname[defaultcs])
+ else {
+ // this looks confusing because we are chaining off of the if
+ // in the macro. probably we should be wrapping everything blocks
+ // so this isn't possible...
+ defaultcs = defaultfg;
+ }
+ XRESOURCE_LOAD_STRING("reverse-cursor", colorname[defaultrcs])
+ else {
+ // see above.
+ defaultrcs = defaultbg;
+ }
+
+ XRESOURCE_LOAD_STRING("font", font);
+ XRESOURCE_LOAD_STRING("termname", termname);
+
+ XRESOURCE_LOAD_INTEGER("blinktimeout", blinktimeout);
+ XRESOURCE_LOAD_INTEGER("bellvolume", bellvolume);
+ XRESOURCE_LOAD_INTEGER("borderpx", borderpx);
+ XRESOURCE_LOAD_INTEGER("cursorshape", cursorshape);
+
+ XRESOURCE_LOAD_FLOAT("cwscale", cwscale);
+ XRESOURCE_LOAD_FLOAT("chscale", chscale);
+ }
+ XFlush(dpy);
+}
+
+void
+reload(int sig)
+{
+ xrdb_load();
+
+ /* colors, fonts */
+ xloadcols();
+ xunloadfonts();
+ xloadfonts(font, 0);
+
+ /* pretend the window just got resized */
+ cresize(win.w, win.h);
+
+ redraw();
+
+ /* triggers re-render if we're visible. */
+ ttywrite("\033[O", 3, 1);
+
+ signal(SIGUSR1, reload);
+}
+
+
+void
+usage(void)
+{
+ die("usage: %s [-aiv] [-c class] [-d path] [-f font]"
+ " [-g geometry] [-n name] [-o file]\n"
+ " [-T title] [-t title] [-w windowid]"
+ " [[-e] command [args ...]]\n"
+ " %s [-aiv] [-c class] [-d path] [-f font]"
+ " [-g geometry] [-n name] [-o file]\n"
+ " [-T title] [-t title] [-w windowid] -l line"
+ " [stty_args ...]\n", argv0, argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ xw.l = xw.t = 0;
+ xw.isfixed = False;
+ xsetcursor(cursorshape);
+
+ ARGBEGIN {
+ case 'a':
+ allowaltscreen = 0;
+ break;
+ case 'A':
+ opt_alpha = EARGF(usage());
+ break;
+ case 'c':
+ opt_class = EARGF(usage());
+ break;
+ case 'e':
+ if (argc > 0)
+ --argc, ++argv;
+ goto run;
+ case 'f':
+ opt_font = EARGF(usage());
+ break;
+ case 'g':
+ xw.gm = XParseGeometry(EARGF(usage()),
+ &xw.l, &xw.t, &cols, &rows);
+ break;
+ case 'i':
+ xw.isfixed = 1;
+ break;
+ case 'o':
+ opt_io = EARGF(usage());
+ break;
+ case 'l':
+ opt_line = EARGF(usage());
+ break;
+ case 'n':
+ opt_name = EARGF(usage());
+ break;
+ case 't':
+ case 'T':
+ opt_title = EARGF(usage());
+ break;
+ case 'w':
+ opt_embed = EARGF(usage());
+ break;
+ case 'v':
+ die("%s " VERSION "\n", argv0);
+ break;
+ case 'd':
+ opt_dir = EARGF(usage());
+ 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("");
+ xrdb_load();
+ signal(SIGUSR1, reload);
+
+ cols = MAX(cols, 1);
+ rows = MAX(rows, 1);
+ tnew(cols, rows);
+ xinit(cols, rows);
+ xsetenv();
+ selinit();
+ chdir(opt_dir);
+ run();
+
+ return 0;
+}