From d8a9fcbc989f6e30e7fb68c930d8b04e1183d38d Mon Sep 17 00:00:00 2001 From: bacalhau Date: Wed, 2 Jul 2025 15:45:16 +0100 Subject: [PATCH] first commit --- README.md | 0 dmenu/LICENSE | 30 + dmenu/Makefile | 58 + dmenu/README | 24 + dmenu/arg.h | 49 + dmenu/config.def.h | 23 + dmenu/config.h | 23 + dmenu/config.mk | 32 + dmenu/dmenu | Bin 0 -> 42320 bytes dmenu/dmenu.1 | 194 ++ dmenu/dmenu.c | 795 +++++ dmenu/dmenu.o | Bin 0 -> 32240 bytes dmenu/dmenu_path | 13 + dmenu/dmenu_run | 2 + dmenu/drw.c | 448 +++ dmenu/drw.h | 58 + dmenu/drw.o | Bin 0 -> 11480 bytes dmenu/stest | Bin 0 -> 16312 bytes dmenu/stest.1 | 90 + dmenu/stest.c | 109 + dmenu/stest.o | Bin 0 -> 5360 bytes dmenu/util.c | 37 + dmenu/util.h | 9 + dmenu/util.o | Bin 0 -> 2488 bytes dwm/LICENSE | 38 + dwm/Makefile | 45 + dwm/Makefile.orig | 48 + dwm/Makefile.rej | 18 + dwm/README | 48 + dwm/config.def.h | 116 + dwm/config.def.h.orig | 119 + dwm/config.def.h.rej | 29 + dwm/config.h | 145 + dwm/config.mk | 61 + dwm/config.mk.orig | 62 + dwm/config.mk.rej | 13 + dwm/drw.c | 451 +++ dwm/drw.c.orig | 452 +++ dwm/drw.c.rej | 13 + dwm/drw.h | 58 + dwm/drw.h.orig | 61 + dwm/drw.o | Bin 0 -> 14088 bytes dwm/dwm | Bin 0 -> 83616 bytes dwm/dwm-backlight-20241021-351084d.diff | 47 + dwm/dwm-betterswallow-20250116-89eeca1.diff | 280 ++ dwm/dwm-dynamicswallow-20240320-061e9fe.diff | 1020 +++++++ dwm/dwm-swallow-6.3.diff | 412 +++ dwm/dwm.1 | 176 ++ dwm/dwm.c | 2164 ++++++++++++++ dwm/dwm.c.orig | 2387 +++++++++++++++ dwm/dwm.c.rej | 81 + dwm/dwm.o | Bin 0 -> 68576 bytes dwm/dwm.png | Bin 0 -> 373 bytes dwm/nigga.h | 133 + dwm/transient.c | 42 + dwm/util.c | 37 + dwm/util.c.orig | 66 + dwm/util.c.rej | 38 + dwm/util.h | 9 + dwm/util.h.orig | 10 + dwm/util.h.rej | 7 + dwm/util.o | Bin 0 -> 2512 bytes scripts/status.sh | 8 + st/FAQ | 253 ++ st/LEGACY | 17 + st/LICENSE | 34 + st/Makefile | 52 + st/Makefile.orig | 51 + st/README | 34 + st/README.md | 4 + st/TODO | 28 + st/arg.h | 50 + st/boxdraw.c | 194 ++ st/boxdraw.o | Bin 0 -> 7672 bytes st/boxdraw_data.h | 214 ++ st/config.def.h | 493 ++++ st/config.def.h.orig | 491 ++++ st/config.def.h.rej | 12 + st/config.h | 489 ++++ st/config.mk | 36 + st/st | Bin 0 -> 113232 bytes st/st.1 | 177 ++ st/st.c | 2756 ++++++++++++++++++ st/st.c.orig | 2685 +++++++++++++++++ st/st.h | 144 + st/st.h.orig | 136 + st/st.info | 243 ++ st/st.o | Bin 0 -> 81792 bytes st/win.h | 41 + st/x.c | 2146 ++++++++++++++ st/x.c.orig | 2145 ++++++++++++++ st/x.c.rej | 113 + st/x.o | Bin 0 -> 77560 bytes 93 files changed, 23726 insertions(+) create mode 100644 README.md create mode 100644 dmenu/LICENSE create mode 100644 dmenu/Makefile create mode 100644 dmenu/README create mode 100644 dmenu/arg.h create mode 100644 dmenu/config.def.h create mode 100644 dmenu/config.h create mode 100644 dmenu/config.mk create mode 100755 dmenu/dmenu create mode 100644 dmenu/dmenu.1 create mode 100644 dmenu/dmenu.c create mode 100644 dmenu/dmenu.o create mode 100755 dmenu/dmenu_path create mode 100755 dmenu/dmenu_run create mode 100644 dmenu/drw.c create mode 100644 dmenu/drw.h create mode 100644 dmenu/drw.o create mode 100755 dmenu/stest create mode 100644 dmenu/stest.1 create mode 100644 dmenu/stest.c create mode 100644 dmenu/stest.o create mode 100644 dmenu/util.c create mode 100644 dmenu/util.h create mode 100644 dmenu/util.o create mode 100755 dwm/LICENSE create mode 100755 dwm/Makefile create mode 100755 dwm/Makefile.orig create mode 100755 dwm/Makefile.rej create mode 100755 dwm/README create mode 100755 dwm/config.def.h create mode 100755 dwm/config.def.h.orig create mode 100755 dwm/config.def.h.rej create mode 100755 dwm/config.h create mode 100755 dwm/config.mk create mode 100755 dwm/config.mk.orig create mode 100755 dwm/config.mk.rej create mode 100755 dwm/drw.c create mode 100755 dwm/drw.c.orig create mode 100755 dwm/drw.c.rej create mode 100755 dwm/drw.h create mode 100755 dwm/drw.h.orig create mode 100644 dwm/drw.o create mode 100755 dwm/dwm create mode 100755 dwm/dwm-backlight-20241021-351084d.diff create mode 100755 dwm/dwm-betterswallow-20250116-89eeca1.diff create mode 100755 dwm/dwm-dynamicswallow-20240320-061e9fe.diff create mode 100755 dwm/dwm-swallow-6.3.diff create mode 100755 dwm/dwm.1 create mode 100755 dwm/dwm.c create mode 100755 dwm/dwm.c.orig create mode 100755 dwm/dwm.c.rej create mode 100644 dwm/dwm.o create mode 100755 dwm/dwm.png create mode 100755 dwm/nigga.h create mode 100755 dwm/transient.c create mode 100755 dwm/util.c create mode 100755 dwm/util.c.orig create mode 100755 dwm/util.c.rej create mode 100755 dwm/util.h create mode 100755 dwm/util.h.orig create mode 100755 dwm/util.h.rej create mode 100644 dwm/util.o create mode 100755 scripts/status.sh create mode 100755 st/FAQ create mode 100755 st/LEGACY create mode 100755 st/LICENSE create mode 100755 st/Makefile create mode 100755 st/Makefile.orig create mode 100755 st/README create mode 100755 st/README.md create mode 100755 st/TODO create mode 100755 st/arg.h create mode 100755 st/boxdraw.c create mode 100644 st/boxdraw.o create mode 100755 st/boxdraw_data.h create mode 100755 st/config.def.h create mode 100755 st/config.def.h.orig create mode 100755 st/config.def.h.rej create mode 100755 st/config.h create mode 100755 st/config.mk create mode 100755 st/st create mode 100755 st/st.1 create mode 100755 st/st.c create mode 100755 st/st.c.orig create mode 100755 st/st.h create mode 100755 st/st.h.orig create mode 100755 st/st.info create mode 100644 st/st.o create mode 100755 st/win.h create mode 100755 st/x.c create mode 100755 st/x.c.orig create mode 100755 st/x.c.rej create mode 100644 st/x.o diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/dmenu/LICENSE b/dmenu/LICENSE new file mode 100644 index 0000000..2a64b28 --- /dev/null +++ b/dmenu/LICENSE @@ -0,0 +1,30 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2008 Sander van Dijk +© 2006-2007 Michał Janeczek +© 2007 Kris Maglione +© 2009 Gottox +© 2009 Markus Schnalke +© 2009 Evan Gates +© 2010-2012 Connor Lane Smith +© 2014-2022 Hiltjo Posthuma +© 2015-2019 Quentin Rameau + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dmenu/Makefile b/dmenu/Makefile new file mode 100644 index 0000000..458c524 --- /dev/null +++ b/dmenu/Makefile @@ -0,0 +1,58 @@ +# dmenu - dynamic menu +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dmenu.c stest.c util.c +OBJ = $(SRC:.c=.o) + +all: dmenu stest + +.c.o: + $(CC) -c $(CFLAGS) $< + +config.h: + cp config.def.h $@ + +$(OBJ): arg.h config.h config.mk drw.h + +dmenu: dmenu.o drw.o util.o + $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) + +stest: stest.o + $(CC) -o $@ stest.o $(LDFLAGS) + +clean: + rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz + +dist: clean + mkdir -p dmenu-$(VERSION) + cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ + drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ + dmenu-$(VERSION) + tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) + gzip dmenu-$(VERSION).tar + rm -rf dmenu-$(VERSION) + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run + chmod 755 $(DESTDIR)$(PREFIX)/bin/stest + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ + $(DESTDIR)$(PREFIX)/bin/dmenu_path\ + $(DESTDIR)$(PREFIX)/bin/dmenu_run\ + $(DESTDIR)$(PREFIX)/bin/stest\ + $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ + $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +.PHONY: all clean dist install uninstall diff --git a/dmenu/README b/dmenu/README new file mode 100644 index 0000000..a8fcdfe --- /dev/null +++ b/dmenu/README @@ -0,0 +1,24 @@ +dmenu - dynamic menu +==================== +dmenu is an efficient dynamic menu for X. + + +Requirements +------------ +In order to build dmenu you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (dmenu is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install dmenu +(if necessary as root): + + make clean install + + +Running dmenu +------------- +See the man page for details. diff --git a/dmenu/arg.h b/dmenu/arg.h new file mode 100644 index 0000000..e94e02b --- /dev/null +++ b/dmenu/arg.h @@ -0,0 +1,49 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/dmenu/config.def.h b/dmenu/config.def.h new file mode 100644 index 0000000..1edb647 --- /dev/null +++ b/dmenu/config.def.h @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "monospace:size=10" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#bbbbbb", "#222222" }, + [SchemeSel] = { "#eeeeee", "#005577" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; diff --git a/dmenu/config.h b/dmenu/config.h new file mode 100644 index 0000000..4ed1276 --- /dev/null +++ b/dmenu/config.h @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "CozetteCrossedSevenVector:pixelsize=13" +}; +static const char *prompt = NULL; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#ebdbb2", "#1d2021" }, + [SchemeSel] = { "#1d2021", "#ebdbb2" }, + [SchemeOut] = { "#ebdbb2", "#1d2021" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; diff --git a/dmenu/config.mk b/dmenu/config.mk new file mode 100644 index 0000000..137f7c8 --- /dev/null +++ b/dmenu/config.mk @@ -0,0 +1,32 @@ +# dmenu version +VERSION = 5.3 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = $(X11INC)/freetype2 +#MANPREFIX = ${PREFIX}/man + +# includes and libs +INCS = -I$(X11INC) -I$(FREETYPEINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) +LDFLAGS = $(LIBS) + +# compiler and linker +CC = cc diff --git a/dmenu/dmenu b/dmenu/dmenu new file mode 100755 index 0000000000000000000000000000000000000000..371b17a8b92fad634829217920561e3d7bdc85f2 GIT binary patch literal 42320 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

M)Q_`X3`G7hdM~Mwi60(+t|$k*Tb}t#|%tZ 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 literal 0 HcmV?d00001 diff --git a/dmenu/dmenu.1 b/dmenu/dmenu.1 new file mode 100644 index 0000000..323f93c --- /dev/null +++ b/dmenu/dmenu.1 @@ -0,0 +1,194 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfiv ] +.RB [ \-l +.IR lines ] +.RB [ \-m +.IR monitor ] +.RB [ \-p +.IR prompt ] +.RB [ \-fn +.IR font ] +.RB [ \-nb +.IR color ] +.RB [ \-nf +.IR color ] +.RB [ \-sb +.IR color ] +.RB [ \-sf +.IR color ] +.RB [ \-w +.IR windowid ] +.P +.BR dmenu_run " ..." +.SH DESCRIPTION +.B dmenu +is a dynamic menu for X, which reads a list of newline\-separated items from +stdin. When the user selects an item and presses Return, their choice is printed +to stdout and dmenu terminates. Entering text will narrow the items to those +matching the tokens in the input. +.P +.B dmenu_run +is a script used by +.IR dwm (1) +which lists programs in the user's $PATH and runs the result in their $SHELL. +.SH OPTIONS +.TP +.B \-b +dmenu appears at the bottom of the screen. +.TP +.B \-f +dmenu grabs the keyboard before reading stdin if not reading from a tty. This +is faster, but will lock up X until stdin reaches end\-of\-file. +.TP +.B \-i +dmenu matches menu items case insensitively. +.TP +.BI \-l " lines" +dmenu lists items vertically, with the given number of lines. +.TP +.BI \-m " monitor" +dmenu is displayed on the monitor number supplied. Monitor numbers are starting +from 0. +.TP +.BI \-p " prompt" +defines the prompt to be displayed to the left of the input field. +.TP +.BI \-fn " font" +defines the font or font set used. +.TP +.BI \-nb " color" +defines the normal background color. +.IR #RGB , +.IR #RRGGBB , +and X color names are supported. +.TP +.BI \-nf " color" +defines the normal foreground color. +.TP +.BI \-sb " color" +defines the selected background color. +.TP +.BI \-sf " color" +defines the selected foreground color. +.TP +.B \-v +prints version information to stdout, then exits. +.TP +.BI \-w " windowid" +embed into windowid. +.SH USAGE +dmenu is completely controlled by the keyboard. Items are selected using the +arrow keys, page up, page down, home, and end. +.TP +.B Tab +Copy the selected item to the input field. +.TP +.B Return +Confirm selection. Prints the selected item to stdout and exits, returning +success. +.TP +.B Ctrl-Return +Confirm selection. Prints the selected item to stdout and continues. +.TP +.B Shift\-Return +Confirm input. Prints the input text to stdout and exits, returning success. +.TP +.B Escape +Exit without selecting an item, returning failure. +.TP +.B Ctrl-Left +Move cursor to the start of the current word +.TP +.B Ctrl-Right +Move cursor to the end of the current word +.TP +.B C\-a +Home +.TP +.B C\-b +Left +.TP +.B C\-c +Escape +.TP +.B C\-d +Delete +.TP +.B C\-e +End +.TP +.B C\-f +Right +.TP +.B C\-g +Escape +.TP +.B C\-h +Backspace +.TP +.B C\-i +Tab +.TP +.B C\-j +Return +.TP +.B C\-J +Shift-Return +.TP +.B C\-k +Delete line right +.TP +.B C\-m +Return +.TP +.B C\-M +Shift-Return +.TP +.B C\-n +Down +.TP +.B C\-p +Up +.TP +.B C\-u +Delete line left +.TP +.B C\-w +Delete word left +.TP +.B C\-y +Paste from primary X selection +.TP +.B C\-Y +Paste from X clipboard +.TP +.B M\-b +Move cursor to the start of the current word +.TP +.B M\-f +Move cursor to the end of the current word +.TP +.B M\-g +Home +.TP +.B M\-G +End +.TP +.B M\-h +Up +.TP +.B M\-j +Page down +.TP +.B M\-k +Page up +.TP +.B M\-l +Down +.SH SEE ALSO +.IR dwm (1), +.IR stest (1) diff --git a/dmenu/dmenu.c b/dmenu/dmenu.c new file mode 100644 index 0000000..fd49549 --- /dev/null +++ b/dmenu/dmenu.c @@ -0,0 +1,795 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include + +#include "drw.h" +#include "util.h" + +/* macros */ +#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ + * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw; +static int lrpad; /* sum of left and right padding */ +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; + +static unsigned int +textw_clamp(const char *str, unsigned int n) +{ + unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; + return MIN(w, n); +} + +static void +appenditem(struct item *item, struct item **list, struct item **last) +{ + if (*last) + (*last)->right = item; + else + *list = item; + + item->left = *last; + item->right = NULL; + *last = item; +} + +static void +calcoffsets(void) +{ + int i, n; + + if (lines > 0) + n = lines * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ + for (i = 0, next = curr; next; next = next->right) + if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n) + break; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKeyboard(dpy, CurrentTime); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + for (i = 0; items && items[i].text; ++i) + free(items[i].text); + free(items); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static char * +cistrstr(const char *h, const char *n) +{ + size_t i; + + if (!n[0]) + return (char *)h; + + for (; *h; ++h) { + for (i = 0; n[i] && tolower((unsigned char)n[i]) == + tolower((unsigned char)h[i]); ++i) + ; + if (n[i] == '\0') + return (char *)h; + } + return NULL; +} + +static int +drawitem(struct item *item, int x, int y, int w) +{ + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else + drw_setscheme(drw, scheme[SchemeNorm]); + + return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); +} + +static void +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, w; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt && *prompt) { + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + } + + if (lines > 0) { + /* draw vertical list */ + for (item = curr; item != next; item = item->right) + drawitem(item, x, y += bh, mw - x); + } else if (matches) { + /* draw horizontal list */ + x += inputw; + w = TEXTW("<"); + if (curr->left) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + } + } + drw_map(drw, win, 0, 0, mw, mh); +} + +static void +grabfocus(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + nanosleep(&ts, NULL); + } + die("cannot grab focus"); +} + +static void +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) + return; + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } + die("cannot grab keyboard"); +} + +static void +match(void) +{ + static char **tokv = NULL; + static int tokn = 0; + + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + + strcpy(buf, text); + /* separate input text into tokens to be matched individually */ + for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) + if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) + die("cannot realloc %zu bytes:", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + for (item = items; item && item->text; item++) { + for (i = 0; i < tokc; i++) + if (!fstrstr(item->text, tokv[i])) + break; + if (i != tokc) /* not all tokens match */ + continue; + /* exact matches go first, then prefixes, then substrings */ + if (!tokc || !fstrncmp(text, item->text, textsize)) + appenditem(item, &matches, &matchend); + else if (!fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lprefix, &prefixend); + else + appenditem(item, &lsubstr, &substrend); + } + if (lprefix) { + if (matches) { + matchend->right = lprefix; + lprefix->left = matchend; + } else + matches = lprefix; + matchend = prefixend; + } + if (lsubstr) { + if (matches) { + matchend->right = lsubstr; + lsubstr->left = matchend; + } else + matches = lsubstr; + matchend = substrend; + } + curr = sel = matches; + calcoffsets(); +} + +static void +insert(const char *str, ssize_t n) +{ + if (strlen(text) + n > sizeof text - 1) + return; + /* move existing text out of the way, insert new text, and update cursor */ + memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + if (n > 0) + memcpy(&text[cursor], str, n); + cursor += n; + match(); +} + +static size_t +nextrune(int inc) +{ + ssize_t n; + + /* return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + return n; +} + +static void +movewordedge(int dir) +{ + if (dir < 0) { /* move cursor to the start of the word*/ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + } else { /* move cursor to the end of the word */ + while (text[cursor] && strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + while (text[cursor] && !strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + } +} + +static void +keypress(XKeyEvent *ev) +{ + char buf[64]; + int len; + KeySym ksym = NoSymbol; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: /* composed string from input method */ + goto insert; + case XLookupKeySym: + case XLookupBoth: /* a KeySym and a string are returned: use keysym */ + break; + } + + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: /* fallthrough */ + case XK_J: /* fallthrough */ + case XK_m: /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: ksym = XK_Down; break; + case XK_p: ksym = XK_Up; break; + + case XK_k: /* delete right */ + text[cursor] = '\0'; + match(); + break; + case XK_u: /* delete left */ + insert(NULL, 0 - cursor); + break; + case XK_w: /* delete word */ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + break; + case XK_y: /* paste selection */ + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_Left: + case XK_KP_Left: + movewordedge(-1); + goto draw; + case XK_Right: + case XK_KP_Right: + movewordedge(+1); + goto draw; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + cleanup(); + exit(1); + default: + return; + } + } else if (ev->state & Mod1Mask) { + switch(ksym) { + case XK_b: + movewordedge(-1); + goto draw; + case XK_f: + movewordedge(+1); + goto draw; + case XK_g: ksym = XK_Home; break; + case XK_G: ksym = XK_End; break; + case XK_h: ksym = XK_Up; break; + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; + default: + return; + } + } + + switch(ksym) { + default: +insert: + if (!iscntrl((unsigned char)*buf)) + insert(buf, len); + break; + case XK_Delete: + case XK_KP_Delete: + if (text[cursor] == '\0') + return; + cursor = nextrune(+1); + /* fallthrough */ + case XK_BackSpace: + if (cursor == 0) + return; + insert(NULL, nextrune(-1) - cursor); + break; + case XK_End: + case XK_KP_End: + if (text[cursor] != '\0') { + cursor = strlen(text); + break; + } + if (next) { + /* jump to end of list and position items in reverse */ + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + } + sel = matchend; + break; + case XK_Escape: + cleanup(); + exit(1); + case XK_Home: + case XK_KP_Home: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + case XK_KP_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Up: + case XK_KP_Up: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_Next: + case XK_KP_Next: + if (!next) + return; + sel = curr = next; + calcoffsets(); + break; + case XK_Prior: + case XK_KP_Prior: + if (!prev) + return; + sel = curr = prev; + calcoffsets(); + break; + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Right: + case XK_KP_Right: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Down: + case XK_KP_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_Tab: + if (!sel) + return; + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); + text[cursor] = '\0'; + match(); + break; + } + +draw: + drawmenu(); +} + +static void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* we have been given the current selection, now insert it into input */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +static void +readstdin(void) +{ + char *line = NULL; + size_t i, itemsiz = 0, linesiz = 0; + ssize_t len; + + /* read each line from stdin and add it to the item list */ + for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { + if (i + 1 >= itemsiz) { + itemsiz += 256; + if (!(items = realloc(items, itemsiz * sizeof(*items)))) + die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); + } + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + if (!(items[i].text = strdup(line))) + die("strdup:"); + + items[i].out = 0; + } + free(line); + if (items) + items[i].text = NULL; + lines = MIN(lines, i); +} + +static void +run(void) +{ + XEvent ev; + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) + XRaiseWindow(dpy, win); + break; + } + } +} + +static void +setup(void) +{ + int x, y, i, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; +#endif + /* init appearance */ + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + lines = MAX(lines, 0); + mh = (lines + 1) * bh; +#ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i]) != 0) + break; + + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = mw / 3; /* input width: ~33% of monitor width */ + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, root, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetClassHint(dpy, win, &ch); + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + if (embed) { + XReparentWindow(dpy, win, parentwin, x, y); + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); +} + +static void +usage(void) +{ + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); +} + +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colors[SchemeNorm][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else + usage(); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif + + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); + } else { + readstdin(); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} diff --git a/dmenu/dmenu.o b/dmenu/dmenu.o new file mode 100644 index 0000000000000000000000000000000000000000..813fb57439333fcead347191e37336a3d0cfba88 GIT binary patch literal 32240 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 +#include +#include +#include +#include + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD + +static int +utf8decode(const char *s_in, long *u, int *err) +{ + static const unsigned char lens[] = { + /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */ + /* 110XX */ 2, 2, 2, 2, + /* 1110X */ 3, 3, + /* 11110 */ 4, + /* 11111 */ 0, /* invalid */ + }; + static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 }; + static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 }; + + const unsigned char *s = (const unsigned char *)s_in; + int len = lens[*s >> 3]; + *u = UTF_INVALID; + *err = 1; + if (len == 0) + return 1; + + long cp = s[0] & leading_mask[len - 1]; + for (int i = 1; i < len; ++i) { + if (s[i] == '\0' || (s[i] & 0xC0) != 0x80) + return i; + cp = (cp << 6) | (s[i] & 0x3F); + } + /* out of range, surrogate, overlong encoding */ + if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1]) + return len; + + *err = 0; + *u = cp; + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + int ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + static unsigned int nomatches[128], ellipsis_width, invalid_width; + static const char invalid[] = "�"; + + 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); + if (w < lpad) + return x + w; + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + if (!invalid_width && render) + invalid_width = drw_fontset_getwidth(drw, invalid); + while (1) { + ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, &utf8err); + 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) { + text += utf8charlen; + utf8strlen += utf8err ? 0 : utf8charlen; + ew += utf8err ? 0 : tmpw; + } else { + nextfont = curfont; + } + break; + } + } + + if (overflow || !charexists || nextfont || utf8err) + 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 (utf8err && (!render || invalid_width < w)) { + if (render) + drw_text(drw, x, y, w, h, 0, invalid, invert); + x += invalid_width; + w -= invalid_width; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + + if (!*text || overflow) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + hash = (unsigned int)utf8codepoint; + hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; + hash = ((hash >> 15) ^ hash) * 0xD35A2D97; + h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); + h1 = (hash >> 17) % LENGTH(nomatches); + /* avoid expensive XftFontMatch call when we know we won't find a match */ + if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint) + goto no_match; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; +no_match: + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/dmenu/drw.h b/dmenu/drw.h new file mode 100644 index 0000000..fd7631b --- /dev/null +++ b/dmenu/drw.h @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/dmenu/drw.o b/dmenu/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..98fabd40b3f01d8844887a7d3bb5618fa0fc7ae6 GIT binary patch literal 11480 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

vXovSNj z6_J)oEp9O}TEV#4lB^;4w`eT>+0SGK zb%sjojCw8dIYcT#9=>Dj-Di`V8;(U`a*D04_gsddHWpKY$FXYEg^=yJ*DNCuG6M4z zVcZBbVhQ8T^%Tepn(C?>ZiWRmspEgf;{$B+^Kejxaby?{^*16@i@N7W#H1+adr$Wj z*PtPxNoD8T?$5Edqb+UD0zl=tXEDw7O|~5SkD~W)08!f?E9b8|O-hODYB2V#I{h=6 zuDJGggng?MF}`>^hb?|ku7|iaIo3{yqc+mR8#k{UYpOt{b7zlO6GN&0jNc|1;0*2 zvhbKdr%D`F-(q9y5zO_rbVN(Os+4{7L|!qH&y|u) zA?&#iNX*s!OdKqI7?$2bc2X<92?l9(O$$u*3lMZUI2)&epO!&|wwF5fE27_Tm@kH;T|>7y|WH@r}-gpQCV?sa8=d zf^vy2WynTwx#Z%MzF4xqjqrH;S#ezf#AM4`{n%ZhK4rjE6n1ojvlc9XCzT=<9S%@C zz(O)w$OQ6VpAJ+&qNL1dOk{;}VU^!ktXct4lC^2P{kT}Qnq`~^Hfl&1B9w%0OY|&h z%3)b<1M**IX<=CwHzqoL6N0qhr85*KWeLY9J1^QsyWRn&CSU7aZb=rauOKGuTWQC8 z6xyxq?y_U>T6RIlc){xKj%8nbl&7b&*_P5X7h7g4tsyk1LXWem&0g9c-oM(tc#@gS*pQ}ekzaVKJdR!L73yy z5Lc5P9peO>=cwjS*n=G#PJ{w`X#t%`27wZ2k7)kpGeHS`D-oT=64*aQg&&Uf5x8FA zh;31?{+b$bvjmU9*uxb3K|RujZ7UlHSg%@6{U_Y3&Y_c`SMAZ$`5YHuh}C*kJ(fz^ z@-C|~__W(2pIMxbP_4ThzfWzv6Do+VHpw^ScFd@3R>aVG3&QF;)|DFHO8^U#CCSHs zk^YTy^Xm{TwFo2dplP%w=@NvzaW@s?1YKV*LHIKpXKKO&&%J*amGp1#=h5RYp|N3J z*Rdf}D@E^6Deij6D8=d1p&=!)%V0$gAh_CWY+2_du{CC}L+b7p){)K1i!o`IvYv=VlV58eDTS8hJqqvVw(^Wz-*T(7dAtRqcq3e3OaMl8 z^az!2+3yhGbCltJvsT~*07wE<`&sS%6`9*JNK!Pt!#=WPY1P(5ltgndGH(UhZ5`%_ zKfb}z)-5T@_*3e|CQtmgP;VY%XAw^or`)ydvdTU zXrOZyU&g+*&AP-8>L#6tmT~7Dxa!6$Zozn)`*; zxFQKq< z4I63@-$>grNGX9Qz3Yb?Ce*8<_Tw2}4 zw;CiAwW!t*Nmf%&es08~g<8}fLnOuui~41V>_y}@kgUreS9d^+zFvcE)%TGns{IjW zI9iyk3k`Y8e?$DmVE3=1?y($KblexJcZWDvg*c~xlS&){#uA_`NUdCsRhQ^G>Q^8k zM;S7$&ELz@NrKu9T=kI>ac%TDlvSv77r+V{?qb z{TZ#fIbbJrCEPjeGL{e3#`1M2I&3u`-HAqFU&dz`8PTKviqlU$JsKrW0lI~?iqTrU zW6wa$P`xOn7wchC8NOpE1y}Qm+gwWAbqM3K)d)O%S?6+nQ|n`#zI)EmjTXt<+}DZ2 zvlRog6)eM@SP`8t{7d4y@ztm4uivUrX6o>8kN*=8BQFpmGT2P#fpZE_dr8ts=7pz`KCe72aWWQi5p%D(3Lx- z{29>0H~4#MOSBwO9QPtZWdngMlu%NJ78B~A(Lfev5$YT96icHx_RmJtKrQMkgmd$M zhA;0QnnW53Xm9R_Mj6yUeq{nqfh)V=C$zdD{UIyI_fF;Y$R3_{o8f7Xfo~-zCEg{U zM)%yansv|4bH+saH2R-^8ivoDeAp49P;N&9dZu9o--xSo!GigcJbgXuS(>T~%IZ0x|1gpK?Ql5!qh);v3lJQ+4zRpn*m!gLZAU*V~PW(cq7!UJOOq?lZ zj}hZ-f`Y$zns5>uk8NG@@+l-<%7@>v|5s#Q7sWW!nzOCDDJMBD^6f+WAka3yzY(Sye~H>eDE&VF~fUp3qedU>x6qfJJ=(dXTf_E37SI z#CIVBttSPgNOhqG2g?o@vM!V}oOsA_WAN*ehm0md7|Do)>*!IFF#->a1NtJ7EqU1p z?60OKlfNCVZ#%L0`agiSnbt`6<3c_w?DOVs2b+?Rz{CV_$@!ZK!8^+0K8d^QKcE%+ zY+hVxL#MFrkZ`o&eGp<>EK~+1VX($fE8$8|bi%H?kd3{ye7}D_c&O&#ZF1snov{$J zssH#1ZCOPu!7h0}%+pt^D1~CaLktanp<59#@)`Lz3|a5*rM5Gu203i%M9*UqF>Ed# zfGT~yg*N6xE=DBIYZLLnsnieZPp2^mUxia~8YT55Gzq7nSj*FT7s^K?beG|Cx_q|F zPCyz~?+p%EbgehI(ml*Kv66KUZ|8u;Y44L2I)rU^S}j@bA+%2JkB!sBA5c7bD~k-I zvWNHVibZi9!jr@(RzHGNSvAEe{hc3>0OyCAm=%bHhva3ltgr~g%Qp8A@7asRHU9+U zJ=;yZpAJzsRaz`@lK8@5(ffD8_nr1`N6R0N1Q=(l?!XwMIIaPe)zOmWj`fYNgs$TK zkApX>^6WF>nigaS-}*LOgcyOfAI<_+<9;t3u0{dw-^&UBY1MQu7Da0Pn2>E)+p`JX zCAw7qjfl$5$F_7?N`Ezu_7f9;_@-5QPb0emge4_u-91p! zqkBK1*dZd2z_VM(z^X_?@J@sjq4N%`b(UaY_yqxVB`q|m&nOu`g4Wu`-2}b>u;F;L z_h+jbr1lASIO_XrFvYvOtMe{^Z(*7G`7As%En~o;evO?nq#wEPKc#yI z96tCR_ z^d5{6tLX~Z%V^E<=V22Y@J;)^@^p^4#sYSn>cb&ElC`|PxY@kp`rrYKKlIN}NxreT z>?TC9VBDERwBee;y}d`Xyl1ap6e~H-Q6caD3~i@HxEX5DUyE=t*y#^<>b4_ErK17A zW8BvvmyHNDyLv;ib$hmbtLTjftMkKA;3lUsTlmLhZ=sfmzm<~wX~5nr(7#M)W!}fY z`>ZMhGsMkSmt7UBy-4Xj7w=w?j6C!WrvY*VSr|}Uz=f4 zS{Y>9KXKRL957Szo<1kFG*Xt5IIdB7GzWdwS8kQxp#a8bx$4nagq+^9qde7>_K_P$ zLLlpOZluYP_lp~>HNsU;Q}W$oMcZh-0Kw>lkI+OU`)<+u0=8b_hHPt1Tz4>e)#TCB z#5HvAMQ`)?5>8;Z6z=wr7kC|OY$T>lYANan&zfpH{X{6jau($jUCpv^55h{u`_tiC zZN@qUPj$?v=o*>W*ek*m6X!&PqlJgSfmp+hAH0c*<3>tdOoL1-Xmu3Mm;KEMc?U^|`N6-5!f~S!PWHb}VFxt@^oZv` zpnY$_aSXNRiCeq2?{v5#{L8_E@*}|}dg+Os-&fafqE;nN!bv{;i_BifnOjLjnQnTJQ2uBM`kw|V)GCY*n`U%QH z-bO`8Fmd)kSaIA0jx=!INSu2x3j13bXW#wiE#yEe1b8$uf&B(vHCCFsDUITL5J{K0r!}1ffgg`k>&=Z0Ye=Cj3!^> zfzbr^viQ@Pv>Q#?jzO3Ww)a!=K9uEo#FTJ3G3`PjcFPGDA*=|Arev{{tUQR6s*}>} zAtO6Z_CuN=(wvhWhY(inWWM)V1?&b*uf@z{XDfe$HfYDyEdmYfJ(0H}TkJo%eUonYG8bHibg1$2S3V{Dxw!d4JRoX>k)#u zgqQHtjNeFXSAvZlwS5Dfx*oMXkN5ztU41|?bPW6Mf3U115kGg5Zc&J&G7Y5*<`bwq zpA()jl5`f9X-TO(g8--#DBe4jJk)7|`!vY|~9_IbpWvh^-e^6#jmUZJ3E|5W4c=@q8# zD{3%@XB4g)^y_3KMF~s3p!4h_pcLU+d5l-h}qvXq?g~J!P zsj)c~JeVBWQb#S=MAhETIv&lKy|9?^-vKWJLpI|A2ilb5F{D#}>4B&$L{&QIvm_SJ zOhuTCfYE#CTO>*XEx&NvA8#b?KuAs)fnR-G?}w!k>%Q6mbo(|Gq8+{jjhs*9evp-p zQ?zcwS&S0*(ow9huqf5K0W>*%!z%3P5eRu4dIq{7 z!%Vl>BCLfN`t$#Bk~XkMv4Fh^kk|1d6?bJ?;5+$i`7pdZTX61WQFo$_;S3vMUN zeuBqz)R6lST>g$r#ui`!OY~{rXI#RPg=-IoGK^$ItglRkCBBR5b)=MSrIlOI7(>7ulsz=k&5F5 z1l4PrAnj75Wy>cvRw<6BDCXwfi0O_Pt$NC!g z6Y-1J@sSG#NCyb@Q?z+@Mjp;ZeT4*SuQevMs;ot;)2T6=wlhxLb@O{6T0I{VmwF6a zNOeLM8dD_K5H|?eS8*X&v53F zNv^}MIt^P?S`i#XdRXxaU{Zcke|j|hXoEp#_d+nbe`|*@5TMl!7lc3ovHAlf=@4Q; zM7a}{1QjogE5cB)kM+e~X>Sl8{1gm|qdyRKm7)aU5WDnR1&-t<;y6>11^PV45IRoe}cd^2QQ>N4uP)dA$I|AjT-U- z(uxn#X1y|QG9atJjp`~$!(4B)p~6)++ALO0fdWl|RIovTBzkBH%+VEKAHYm&b)~7~ ziNx7KcK%tZ;b$CX{D7Bu)VEL{g{$&y!L$CJ5ba7ku4H_RBRq8;t#qj^v)rN-p#i9p z&>dninA&%Wz8hKOeSKlYJ}u+e<`4I#`&w-NVTi#EVw@BF+%hgX83jQWDZw*=Hr(ll z7`i6co#b0kt`^~fSd@`0Ah#$$`E>Q-5P+AT?n9FWiUvJtyM|5 z523VgG?$)D%KwcW+Ti08`5&_{D)>gns9$cRX44ZXv6z)lAm3!1zWHmgoo^AIInR{u zP?qm^HyHWG?L^RLyHLu{_!uE<(&Jgiqfg@d50tU)OGL)_@&!(dtmi`ae_GTTEZ>Kb zP!YaFHdqCxVFiTc)3fASi9!C&!aPJ^f0{KqLtKN;k>Q?0J!U1jk{WbqA2=Kp)Wt*h zALnh=9V?2n=^pH_xRcNPb57Pa0{3FN*X)D2A9|k2u^v|!VGSm4!}aM!8f;PqqlRV`z>D{dCnGZ#9fha zcKMLEgBL1IWec8QcicH#dtDom4=tyguo$$tk`NF2Cjbi78)SV;&I2g z2}Qk98>Vgrs*ZgZEh1CBcmo?8F^BHPc%#k!wl$a$U>P8H@c1BIU4-#V zNmvaytx-<67tiWlD+LtpjwWh@cq7|UKw9`lWU4bDKJZiM8wRV6(ENl-lH}h^?$xRk zdJ$k)z4HTV|B~;?luF94y2F#;r`xXTqY$HHdV@Bxx+ihS#&=jG?fjkYuS@nePrKx+ z($e}KNnx_g@r|B#C2p61R_Gjqmou^(8alhNxkLKm0Mzc$cdOLnk*$@;5NET#rB)}q zzwq{l<@F6+D1bFUJYADKbRN~{_RN$L<7754+_21#`i zwH3P6f})xnqE)2Ws;y)}!zbFL-to3ke^)*yO@1a>Sm1pb1-RaG$aK*PYt>OaBR#X*m= zGQ7t7tNewjPF?hp95y7hRz&A7oq)E4*HYjMyBlkb>TlgWI)sM+#(OU45a@FQG45n+ zSDrwoxEDp;MGvUG4yK585y*Z9m;&C;$j?B|^ zgU>Yx?de+__P3!Dr8Hb%I<6Lw9Q5=wH9Kvq`!$?H;l4neXN%;!)5@-fQB*{pm^~#;ELtF3GbTBLi1M}y9 z#&S(4V+k^@LjvpyY7Q7lhEnIo0F!BIMe8YvcdJAP?*ZhJjoWY$?@|lXht(^>-?ySDbW7MLEQ3tC(2=SLx4F>rT)4LE6YkN`Ong^I&GFvT zquiOM%LsJ-ipyhHVsNr*(HNiT-o{vKa}h|V5fZ4dv?T18aZlSMH&#em5%@%)4qg@ z?}dJ$S#(9h=$Yc$ARv*d%x=F#7zUR#1C_alQNgfW2)UjnU}qjZ%Xeh31rH zW%t3TLBv#!f&{Fo={7drlKB?zjpU%WCg#Ye)Gb@s;$ytHVQ_b%-_U4G+P3aBu9R-# zW|iQ_yTaYY&20p)3c<16ot0;U?pa>^+##zCoi^{jXlKL0C|3{FSz}A_o(a0g$ZZXs z_|OtYgQ+RqupaHv>O>ya%HM--2`}13(unKLQ@0kzwzrKlHb{MtD zYJ5HdaF!oF2l{AF3=9R}ArsfXK{KBxS;{|3haR{Zt+;K;%p*Vx*}Yh~`Cp{O?7cOc zvdorD6K2LJ8Z6vbhm+yULN>fgK!eVCBwKNO^}R7<-AB6I1u5#A7;*hmnO0~3yHNAc zY6i6G(j3PDP#=4ZHAf8a?(-q^Z5jumu*ej4-vlj4!B__R- z;Tmhr@_gt$L*I8e6X(wKp1IgPP5y=^MBM*^SZ$7QK*zv~sOTy{;qN<8>s$|G1Y|@N zb&0}aZTws){})92=b>eRV`3K$ngl%*o2|vBQ7lHiP#dbm$gWNy4k`!MaQMUcwCWEG zEHlM*t3ZyvQ(Pz069bJ-SABGbW%kXt5NGz~^doULh!Zy_I3NRckqC&!6|idMoQ7z8 zQzHY>=*^_|hX~Q(-|BpN3|Yh;83lN-e~Do90?%I{Ar1-D`0GiUD`k3UR`d+Pj5yy| zRN>QEtai6RY*1a4$y!Zy67AZP*29QR>PxTc3n25?x$upXA^&h-Q>SFS!XA!);-St& zZdfl?RiX(f3AKQ*T&VdQv{i)0b2##o=v!gAJLLp7;uN8rIIzB4O&oDg630FC=5Q{K z-EKh~PSDo@#Clc#U|w`=bB9x~;Vr4dRypBnV#!ZP#zT7>*d|5Wc8l+3*$M|)Y=t;Q zaC*NADnqE>z-yd^2PTfS&m%Wz07Un*@R@_VXkUsq$#Gxgac`!@{j7Imfw?sXOCaUhC6(%xm3V)Fz9HR9Bv9HRw8w%1Z4Nc0#EUy~1 zq@R*74v_B#Y8sOoo;c-@Kj)d^6L@PVjv+BX5Z-j#`we^YMC zb193bI=$zF#eLzwz{;bc0%i%VJhT_O1DE58;Wuyzo|wf0{qT&zvlpK5oCLbz300k^ zzH(mrspL!g4lbq~-wXSoeISRQbyqobQfMHpqUXf zR3DJuL+1z?#Sk~#v-iaZr^GNb1O(QqxyE2r97c&#Bhg+?{sUFlBK(Xn{Q+)6kUp|9 zMqU3Y;$-1-0IaKEi0Z3N7e^@RU;jnX&20CGwWCVu@LnKAjec2g5F}`h_JRy{6xa@f zV=-~uOZm!43n-E7NAiCm#9XN0@DWGnKjD`otpF?|V9hH?GD@gK*kimntbY9ghN_cz z`-1-Qn~Y*RiGzc{@xUF%AeuyXzNm462WzMk*>Y=_H8t(uu)e@wp|+2sM>nK_+u|B1 z7K8jC`X2UhoJ0=loi8DpK8JvzwJ-ZxiddZiRgq0%lW$_X<2a-(!YAQyh#}1S7ZoA4 zGw{}`;LpItb+jy#@o`Z4JfVHB|~A2at=2Uf2KkE3aIM6cOAN8p*#X#K-dE@TAIVja_9e}Aa_cX_9@d)?x)#T0DXP4 zth-nh%bjVhM(#CQ?m2KX$@^iWpE8+^a>@LP<1~DLzV5n-lIU76zFCWkAHnL2Uh|wjtV7AzeI1;yZ+zx$ zh;oT<2wOop;mX9DvESm0KUcQVJ(oPFkAFwtc>OQpmBN+K>KAc(6MfH*mioXy0{#)6 zyYSpi&JA|y5Z7m8apwk?2_YhuM0`j;*i`*M#*$-G;Hsd3v^TEH;1@Rt@jEpo6TdT3 zM*BaY6c|I0LO94$*ixJwEmlvQE+=a{He@Mqji%_jWyL@-SITHjm`k~?(0QuG5+hc% zLyBt2LeqP_F?XP_xcDPhEy0=`*MY{1@6uZU&Q(E+&Ha6$)7~Oh6`;(z ztrBj(mTVa>jKx6}(>2OX*$S_~MfZz%TdQUd6xt?M(U(#`WTSyk=w&j8Saq6WkRc3} zl}RRHcP_~;l2VrJ!f2p4Mk6Qn-RICE{{T0e{_!0j>ay4-B59!uievx46f>50Nh%hZV`vy- z4Z(@M3mU(qEKOI7pF$U-wq4?3M8rW8MfLcdVlqJJ(o%&cZe4tduhCuSEW?JTmkJ9IhaMfOkTQi)Fdz!^J zPI#N?OAWU1@~0SBhb#b&*PElnH};5c(8qyb=>6XPI374~YF9_2;9go-{Snso4kt`* z@vL-qY{e0t*KrOufG=P$`ZpAcrE0Ufi)et!I-vok@mJWxaTOlwBA6j?6*gEl%GCWdneaU15Qs5C3clIHzCIQ` zd~A2|{@vi|5bhm@=}V+-Dl$*_ z3b`u6w{?`7-neeX3!`FHE1B@!@iC`bN%3R^duqXf2*zr`6a;%w^yO48IQc^5Dnbq@ z*Dm&OOu<8an%aqyFpuTtOh>$tTa#GjX1R^ib4wN99jE7ZC4zcxgAru84W(NYS(IC9 zHU=Ce;S5PrGUjTz9R#JOBR48K&NZ)uY}6cm%N*SByW)my$JTiFc^yJISYsq{{Qt+^ zyTCNf1i(|n`iCkwx0E@ z``T--{W%cn1P|B6H8OA3H3Ogq4Zu;{V={fMp^=w?*KZh#zw}hG$uuo30b%tR9wcGN zOPjM^MnvKn_SPe)8(4pbhVaaW30U(yFoFr9yIDS(>H7%??B(TX(igcG;ZxfDG23xZ z<7nCy1w2+IQ6LakX!5g6c7u@JVQQetjH%SUB!PHQj$7$16#gDgChQE1K$9R+7vh;= zIkKIG{&?sMK@xzWu{g1LUI$j=vVE=aE6xOlHXml2KYcmi{ARM{xF3cHK1ha5sR zruv5wqUv4UIitr8Il`^lP8B$4VlT^Y>v+t}sJ7tYi*N5HRF%xQORypr2TF-($SP;=r5-#e6iX?;u=4E&UVv;}0~ zU(pG*UXQ3=qSAr)KiHrd9t{iJ|0_sC(d7CgKZjkQG4zdu9zke&=L6Y@_U{X{``44Y z*n2=+3L+~*qu>ejnieV;JmYz;6Z>>xPbKy_q|49)ylNx8j^cdtin(*=TANzUO&H%d zWvZ>+Du^TnN7q9ETYedi0wm63OtktY;AxTvZxz4qdmD{$m}rdA5#yz8yk6dbahS!o z%eRO1GPDU>MXG#FG@`yH)Z%NUhb{DG4y6yvzuyHyu!2d&i;7Z0XHlZ>4{T4QGPEu5 z)V)D7{Il8_d-yhaj1}B|Vkmh(?OT9AA`*`70I!=4#~`F;H@1r7U8jBE`iOIFA+F=d z9O~)cTgSEGxeRfCbFvM?Jn`|;orV^$(vLrq%xN%$^i&kyhZ34)DB>+6jYsc-CV$$S z6kaa0ZtHxR27{O1&`)*o7>-`C`i=x1Bxc_mn<*;xD*FJ0_ykv;SQbIOy6=0uGXd?0 zGr`F@NWB0#i9|K6;bS)-A~=+3#H;$DHtkS@gUtQeu7vo(kiZd zktvQAiYJre{y~bDl2ipLz6Fed$06TYps;lCdvUgD#r7^m3X2Cd6Nj1VYN0Z45zF;$ zSZO!a`G5d>`3J?O5i!pfq64_`R-mwEn~6whpo+@ig?lh&9t#ZJ7UPy=whQtS=Ee9U z2`GbTmm1O@y99aM7cCr0O+B?lH1k6Qh7Z7Ex~WlMXr8(Pte68d`V7B-CT56+qE75S zU{R#Vi^ge4w<6D;;_ONa|q{RRpYn{ zf+c{ho~}aI3!xJ*4MK)j3uqPGvx~x;5k}_D6)b-fEDr(Q_E&CEe5Xa#&%>hOTsbC} zpHY?v)srYN2JYq>!s5u9{TH!ZeUr+|XT_%BpO|h|A#SSw4aI{PUIIhwPq^Vxy*yne zK9ZuM?X#TlP$%IRdJ=V2Ct-Cb;Zq_k10k@EwX;9qLA$0S5-Vs}s~Aqb3x*ltv~&`M z%!LySbJ;mNc-#2&w)`R38F-R4$vZLWp>**^2P=o=*r38B;K%b|PTX16FW6i$+<9#5@jNko<9)7%TG3uV8T*Zlj1$dQg1QJP0eEzSPc9hsY;9 zrurI2-JY`T8mNKsn4Lzhg|ZyK;kS5F9r+;cUJl1H5Te2(lBA;Uk1`bp1U}Csa$-})rvJ#4X)@XmSg8~&%{umr`rTB7(;;e28WF+j&$b|Eh zof+u_@NpU6>;ZVJ?aPR*O}h^EW4o<&XJ|D7KDXKDj`dBA^GzLa*x&*!)RSpl)knox z&u_@XJv-2KHVfd;ujvTX?oZrI3q!TXqdYO2F_J*Y?CFDTY<(MRTaMN?_VQ&|X`Dp4 z8;;2$W?#9pPumW~TL^!0PuPqMZXXQ4`r%h(s+n4h4nM+Zm#m%bk{3sCZ_k~-El`BU zL}ng5`#D6dsZ$a>XX9osze&N%d$m}du3edE_F|$sC7n+C#u4Y(*Y5qcHa-Pxzqfr> z8#w}j5WXZ*c^J9+W)Z=Ibd=;Qerenk?Tf38&v%(q&oq=~@B=$^M z{CHx2^a31Jd31m`Z5pP&32g=fDRh*`P`2urgFP#;t(PM|P>_4Vn3o5@tFi(<4W)sR z)ThB4r$&~h`c@L?`r0;Y$gO?QM40Ml5qVYod>DfSp+}62K=5UnHenRG_Cn_Nr2v6% zpu80{utyFhsH9Uh{2VmNZsY8i6Z`XE&!!`7lc#cQ5@9buy==SQ+EA_Tz{|zHebynz zU+)c7fgLx)bX2cz4jZ-)!VuomiwzI=fo8SSmP*q`48*Yl63WKfwCocb*!Kx$dxmw4 z9}6v5L&01RO%qHf@_rw5vZE7B8>vxj3K-~H4}$M`@LA^Z#^u!C0jwyd=UyYm!PuHd zuJEG!B;938l9h#t>YVx6h`KL(-lj&iq@k^mSbdPV2_-G{Ks>)q`{LkE@;#Y^+3eNuT{4Ir#ton`?uS9ichT`~QwHmLYm~O>LTghrzpiUqZ z_Lhd>0P%md(#!0r!_I-Usg9aFoGXOzksVPrG{L8Fe){1ZQM7J#_L-o zZ%Z|_4TO*C>4<~2kMZs&l3?*2w5dzz!5yUQqoPPMGu#pqye2`-k#WKvo({w51Wd@o0>50kQk1u;p^hxTavv zPTiw_3W_gI54HN|S?NI0Tx;#hbj6F6A~f-kLw!Q~Nq;bSz{ggoeyoEd0u#}g(;hE> zMhZ@t@LXX{HB2CP*4;+DwMQ>7)!jrPzdOO|zb=+$f&5^1W_X|(O#;m-e`7qs`#((e zy^u8I5f+0n`!@U=6JI`5^UsEvT-`M&1W=eOyFoM5&>lUUr$u)8Gtx;Uk4i)<`Cq2s zcm_+sd(auUp7n^c;f!H7Qyf&gBT&)3(}1ZV44oi19?~>fd=N#<>Yoaaow6c#u8bco zALi3cVQ|zZQ4HX>`4d^J%TZW)dA$%uwi-w?&KLmd=nRU9&5o!m7#WGFV9fqMFF$3!(#|chk^JmNPiU4t+o4q_r*MhAT4uZ@IcR% z@r|BN-a_cCjjRQO=&N`9ME!#s2(zb|IqP*Alc2GE3myyn<$Y9<&0X*>5Wbf18&0if zgro4$=^%@8Evvqv#CM+hGVx0`CY(wL=B(jB&<2>-DAktQ&*9`R15bZ}&H>LR5nO>F z-c4x51~AA3W<3D;AHDo@Andbx+qh`kPOE=@N?_;TD4Q@g{~|EFh3c~$&>S(`(@#}m z8!<}jad=9u>M>!o2-B_PeR51-Ean1jzYu@t7689%PD2*V>s@8?HEli#*7q9+8FpcX zUO z*$(U1KO-W}?@zlJ_&{N;Fjk7m@_dPfm8=kAdTKSfU`MCM-=T$;O#CsAP{^P589?B? zU{zxM&a7=Sum;uD1(ZqexAZc+fez(}fzA;Ka=~ckQA%nh2KhAdhZ{VYMPSI?IoiaW z4?5r$Gh4kQ?$?jP6Q5vta0{sq{JskIV|YhTXi<>1S}%<`n+$ip7eHC0vHc;d=@L9s ze+v`^6rkQD)LVy4S4VbWdE$^MugUT%x=VzjB7X%2%MQETQ>>R7=-dhHS_szEXuUM@ zZ$L{mZ<3nLLJcW+RVc8S?%0lPsNdl5rkDx{;G>yKnVf4t96p-4kW?c%2S1fvKD}`6 z+li+}zMleTVUezto(l(lukU8~i&gTVNooluJTy^33b0)LHC}g5H`N>99ePUx-yETs zFyl<8eeLEPtW`x*yBhmaI{2|16Sx^&2im=GIvEKa>A!%sVr(3X%{%$efTiIPT7797 zL906|}$Sd~53BaPL8j-<#_nAMhe^DB*Z#_qy1Q*->rI zEPok#jo&-Gqy2`FFqt1k5@zx3v|#Xp(M!C7NMRO^tQ@!=8}DI;xgWYWnhh4O`n}^s z)eaPuaGEirMKIO9MP_3tiy03#M7+!jN8fj;aAd}n1QnPuaGSy9|wqyLI-k;vC10|KGSuCW877|>p|-ayhu^^NhM@=_ zHk^(3`@=jJ({5t(gq5bcb4d|pgX#6gh#EYizW*-nf3^dFv0=OMpdDcEk8%-CMZ5JY zDomSDjcMp=^;eluGE^uIELcH4x-K#B{`D-7KxM@ch`)gb2)u10$N18&i@~eK-31pa2r+sDa!}b^nK{HXEfu(Dj&gp=R5;* z_hL&*GB|yP{`iZ>sBv^N>}>p{c>n`x1ponm9eM5pm0UvSOjT;quA zS8&lPwa<{jvFivs5efE3zQKfE!C%(qU~Y$o!|@pe>_4*?-tb)z;{KR+>-Y@45ym$? zNGNRu{sumO3V7Z$2hBoP{jeE@ET|^d#XSK88o^>}ZHH#k!&U3xH{ZF#hh)n;Zap;tlwl^>k zxc?lABW4AGhUBXzP~A-{!Cl}BY*eFt7ruW(*=S^cR)cpUj@QpJW&dhvI?3Og%JzMX zXF%ESf7g&P5o^2I+wejaDqS?TcH@mIdYDa3OA2|60*MsJ@yBn>VQV8aL*{#bGE>_u zrku|kGE_)o+%_!!1{iob?K1a$&o>lPqr(PR_%kQYN+q}e4+&|D93Dr|gz_LtAPS0a z53~k~;0y7>*b}&?kz0YaVIA{YPz_t#<^3_dAC_(3k<)AW_5zxj;sdkPEA-02tv~H- z@=6&>1kET5QHf|NGaf0Sg~r`z$9JJ$7}_LTcC2(>xr?n?A2Kay!i{0iRP+&GX=X{G zzC|oij@X-Hn)F8N1<)B-L-mR6WN^T3o5@1~?*`y#4+!tGvah3ACcYeY%sLpRdBeGb z+9Ct3J5l)$^7F)CalzTJ_!ZPdv~m;arMedK1bdE`MWkk`vV6V$*X69VV22hzaX;Ii z`35b3d7ed!)zRGc4;=j*6lJe0I=}V-s6$&fK=ER@O&em6d1eabQ3$(w;tylp1NlzH zO=+!0A>w>+tC?Pz@9Y~{m%59Kf|vjz`G-2%MtIh67xbW`rPbq@mk};DC2Zwxy&5o& z9Jyk<^yoL}fLlyA^+I%8Xqh!&`UmFx{`gb~B1Knq+K|}A1|D0d4Jq6PZW;B`XLB2n zl~IuwvG4-Q-xa%AyrvON?tX#59!g@`cX&V(I0WzF0rG%Wya~d}N-t*kh^8gIS4A$R zM>rUhu1a=^5I1%2-=&Y>LCg|_FH>Otvsi&>yPpPQm_Lm-TnagFAAiinfT{B)U^x&Y zbbr+ZH0brT(2NFW+cs*g{f2K$w4;2*2zFr6$GGX-AN3C1_z_Q964V54t)}7p^Nby5 zY&N7#KMUAiq^==t8HInxxDw&VDSVKJ-=pxi2$LUvHeD48ZN*R#`1D)R9Pt+O09XQF zyoWzGeJ^K^uo-Tjee+&i@r*Onzy~`wA|4h7`GiK=M6f2n_q{NEDfI*u=by zIPrfA?XeE*y$_mw1{;uVo9X+NM|e9ENqE}GKYrje(D2C;-#)gnE;J??_bi?0fZu@w zFyQ_$+n@9qZ3TXfx|=;1a<*-yJ&OGrXx;gn2$4*TsY7E9BSEHwUD&!9=^jd37>B-D z?7Q4(U3DQc4u=yMJ_G}(hATO>xoJPXa{ZpQSJY0!>(B(#J2!F2nIHCM0Zm`?3o1c% zan(R#EzDEJ1~m~p&mPAeQamFPM$rnugLr)f#dIR>1LKy06Yo}B$g&{qQo>yUe%i?A zPxByl_F68Ep5}8=@^l~`LJZwF&6N2qHRy?KDd1%@sCd3l5&6@;gmB>IC9KP+o513v zT8;M=Ku6uq#5ZIpy6;%)ixb1SSx5VjnW$%m9_TTxrp%*OU)nj)MP)cpw_iTT@y8qn zlD`5(b_Vhh6|`4S*o#)j;=}jjHX0&uZ^6R@ME5IHn>G$S43OC{6`08H5cC^TVa5|t z^o~|cKeD}Pe@~1KE`Xpl^tJ${X;y6Jkgfj6a8MiO^gii}ISJwuW)d-cM|@2!5Q+JL zY!AJGiKGw69J0$F^9iU8nNcUPUmhX9;mzKRo3Da%oE z3lKDI7z{4MJ*3)axP~|z450wICH)d`mQnHnP_Dtr(TfOo4aRxJ2;dAw{N>)c0t|sc z7#lLlFxmHWe>XPr<4IKd})Xq(ydv)1N$q1knGJj@NiWIu~BR zpz;V%{>aCW9)HYVC{@`1!9d!#0D%q5ScC_OZg?LnOsw9A8UjoqDt7Waun?+WB<}rW z9<||yIk?5!6p>ayK3C$;?ffwhF#hBRL4a5diM>K%{3MG&2YXCxSk1_hP6lG$V<34k z18F$`fs46en>Z?!Q0ZS&?AxQLb{xZy|Mm6cf1Lfn_$w(PSXjW)TXH`x6-tB^=rCwr z7ljqYO1%9B^3Rk`f7&+y0Tak*DPm}!RV?3b)sKS#T+R%+NnGv~F83tKeGD&4Ib<-2 zU%~;J2Z|4Jom-NYk;L=vQ|oQV(CWd0Lc&LqfjGMlVYz&mD zm^38EBg{vUq1Q45oiV@>hbn;BX*kf6RKtTi4L^vW^xQYp8u`WKu!j(4%=Ec>R%+ z@Rv3Rv3*IYr0N5xYCZHD+Dum0u)*q~uey7796`)?!*{LwPQdl|!_PDhpt&4QB&?oF zZ?^aiM$+Gq&YxRnryi{w%}$!}8-9d1rr0UHR`&qgm*TF^>JZ$Ke~1>jT4fE9I4~b= z0-hKKjghcWCZp?z$+-OhWbcQp6=$n3f;IJfcYp2HBvOeCOJECx`vbjpqu1NY^}Of7 z=H+j({N8E}jhsrFaHew)0?BtWkajLWU?%DqqPheO#U@uYmZ72(uiZXRYGYGNEC#5(LYUUpB$^eC zLDb~tp!sv{57>5u?SIkPz7Mi}Z|C@*i>pBrbNsKytp@Dku@u_ghBJQxW}28_s~_g= zZRSku=EgyA?_>gp4gWg>eXyGC!|MaJ?Pk2hlC{_L;-}b^Y0kmhY%jx#4s5}dEoa?j?ZvorL|n-%E>qpz@QOdCT33L76P8}xqtR)oVG@r?p-;P+4;$VJ#lpR* z4ojfu%(mhN2g|Lj!t+1Vt#3m!4yIgDfcmuo6!;|?J3>txBX7Z-#k4WdFc)v()5;&) zfQmLG8NLdbU8qJ6lfsziL#RS_mkv0t5sLw>cBHPpap}da*Fu1vMAnxekmGCV;wEck z=tbo%Ld9|k(`6&~#Q4VMPXG|Yw9V+JrG zJhtpA)I>s0LI~M(Ol2|gTngZZcJT6y;`VXd$o{}mh|o+lrL(=p^XXbTK*X}ZvXH$< z8@Ll$Nfng#)1;qP_b`^lS7B8|L$6YV*GyEr^r8568egIEVa*i_uQZ5`;)7^p0XcUA z1OihLJF3dL8VfFm1?hM!4v$9hJ+(KGdVktt5<0h(Opbhz#Yh`&9h^9wh<#alupJ?g z@5H3#@xWd<|IduiWVQwwKl)FM{~GkbEwJN1GX4$H=1+T?g!*#h_pu1b_$N5=HX<%y zL^A&25@vjVVZ7fk1oYY#m313k8p=HS;CVAv&7ys8qs<#&weMhc9Nz?{4b_ZSf#@-1gfGaa!osEdhvaVpgWt8mA>OSzN6Mb z)L^2mjG)(JgRFsP%qXp~^cKJb`kZ4TUuUz>xf^tNL+u+;^qIv0E3Su)VV2bG*LzGa zE(|MP^&7p^=g*?Y4^jTCa4O+`Zv^y3P?Jn=j6a0c$>5$QtNxo6LyN~FBK|!?q&zki zYmYd&8Y>e>x;Yik6mTLl?pm=ffaff|@wXL2HoTSb&B`fzisF)Lu^|ahGM;9Y$549` zbqG->)GUvisCcj&zcel#h7v9;6E``?MhxiiAoNxG+nP>lgER6aAVgEi`;^X%)Oh)S zkdf=(LMcGLwPqj=1cDFwr>aPW;2H%}Im49S6(!eim_R^2`6hc&z1PAz3{`v}p_bgt zK-yw}z+}`7P*#!;YPX~JiU8K^xyWy*UJev>aQBMgjcNjK6pOezzwLvkBxBGf(s>}(+Fj`w&N`V~WIq78h3f&b%b#`=K;RIy1Y|Cx zE@M(HTiUS1LLYWI9}K=jy|y0tXwVMqZpk|2OYQ^I$o419UX{HP9+RtZY~qj-~fI z8!=416@wJK+{N0tzU4gr^A_Gfl zHv$rjgv&(NpADb1rQ^8;BE^m{M4Cn3=n|=!BJIZ`h4c0vLyBLZwgjK+f;uFcZqUdN zdmbdL7+w#c@HgG?T=a7cmC(;`Ay)*pp)aJTlfR)B_|vjsQaztdVEM_HmWq3S@(98n zHf6T^lH(8z%)1x7$$g2Ib{-*YKTt+S9yvz281vvyDk%V&|+T z>mrL8h#AjHT;++tRye?CNW^Wc3o3US&O(q1QR|0TO8aBG?xq-S<1rv3Mvz^XFuVRJ z`)njNzfcHiQvW2W*Z)-N*ViC*pg?-gOH%X&?mte~7Lt0YQ)&@O z%_1p(a({%8g$K>@Db>5!YS8+3niTsW)w%&8-^*i7okJmIU3 zh5mg3q8OBHXR?H!7%HZ6pyG5UMb zJF{HWbAD~w0oqGXJ9)xX@42QI&O-cZ%Y z;yX%fxfWA)d(+9#+KxVaUVv}7wbynSOb=$pjn8bzT%uw^+jQW9Om*j;j?9M1_-@J0 zOw%;%55dR^<1O@n_`Eue1!7^6l|EX9NoQ*`TD{`>AD8yQ*E%O);4mJ$@({FQCN*R) zwqN@2UhxHL^PZy+q_0$&Ps1iyd_Q0xRy&g83*c}w&&(rl&}LF^UkjG(Ix_3`o9aFX z5o;CoTTS)v5#L^%iI2VW>4)FI8^ko3L@2~mPelZwFjKu5)es>JhjI}@n`Ww`kEl^p zwcnU2o^&Wk@&)2+NFQ|7ej7z2Hhc&P0@Q~L59bk)aQ#T!;K%|RUp5TwL(?@3%n}+R z#=;3X>L(W8Cz%al3G7XG+7aSU8v|Pc*PI+4C1$Re~1IYBY1M=%PN`kkzLag(&-trlX~E4D|2B-w0D36;_0>>*ivVID~NG_Z2*Z@38w2f_ys3+fZ^rkM-VZ!F1i%+!Ww7 ziawWTZkTNF9m0g)l)V*)15zz(xFU!L#iAi>NkgsyuYa4Y#DblRyxz1!Ew49yU?#6O zBX|h)W+o5em4XT$GMnn};h}VVriq7YOm(z>5-E?y0pMn2{`oWqGiMF?3eK_&>CBFc ze}S_K7orTj1AgXfv;|l`$ZVKK&dxMtzinwc778gUut9GA@n=XTkmNcciB*Rmu_sxG zy}=^1P*!yne2xSUEP&wrAi?0gg}FVrxnyf&a80Qq;b%$sdC=jf^a+(A7kLJLJmN7ZK02_~%S z26^J^Ltr@{ENyp@qrt;{9BemzuPAVQKGb0KzB5{xC(s$B?+)U9hj?d$m-2)jn_P|Y z7Hv@;g4ZMPdIL@`2sdrSbIs*(>82NFApDlPtHH{P6HY_M)CD^Z=;Q-%fdQZ!hO6j2 z1kM1kM{N!+kfdQj=S%Z>{BVSm2hoT7lfPpi?I=Lt9GE7vOXf9O+j@(r1|)<(8Q;c5 zpX8#i5pfqecFqYdj}IG((X~>&hdwh9Ng@lo*da>C zY5Wfp|L0eNe=PBfO51(mSVFwd5N`~4S!;~eW+z&w_e18-uYFlLTvWhHTZcwI2Z6 zrC<|1#BImXlA`m+Z6Yn|da6`nUylchzLY-YnFVdVznp^o$!PgQ*ZTkt-i%!K$z2+sT zv4}SbDI=GUr%XT!#{~6pREnkPPmqeQ=dpy5b0$+`-8Jp?#L@_s*7Bn?v<^NR!+ZAW z$6^FNK(?>}H`X`U+#b$8^E=~#GafkOfioUBfn{6IXr8&OH>@8nh?y4$3O`pJzyDOMX4RDx1^eEMH_UhIJLrB6EC6k-6CBEOiu(GsnAo zEA|p+X^|P}D|413z9Msx*J1X!%x>qRa$D)C_}m`P(o@bbmpaN9c}l?OGMBo_7xh*= zFw(iqG0qIb5EZ+lc%dr3sJhc^Mfju0QEc;;df;>&ryx_{qY_(r5ll7POKg=$;W%^F z>s-LPer7AoY`5v#8!%Y;N|6- zwD_W-=881R3I6lCZHwSV#4>-F&ADVDfZc2_wYl9CEJi+-dnmNXyvX4~!K_?Lcje}C zlu`;IJur7+Z*%8w{;)Y_kJAGr$evTdDsz>yyvSAMECONvFp%x8vNAZ_Jl|Hi$ZZ}z zeE7mnJ^z>O8CGg8b(TBK^QqY7cbWqy2P~0T}v~B=C-)TFn@*^*)*csCX14A(LTlAlrOa=RTxvq|s@ zMCPg-SK+L7lu}(x8##KM&FgWMpyZ}`D!mS}lhxx&TNUcE+nr)AFEtNy*h^gIVNRsQ zQEnb)cY9nFh33&INr{P_rIuQ`;zis5svtZSRh7JLK_zl|J>(NAdkX64d?jH)`4If6 zq(2V)p*k@{DPxJ@nF3!@%J>c3U*xUeVcsM;s-4svRXNejxIH$H*UcIuXJ;GTwM9Y& zVwqZgF>(fqA>&!93=@{RP+Z90@$PX{)##QNF!E-X_K-*wXzGI7NVJ_OO+k&O(`EOR zn&)KC$e5imXU>&fXp0=4iYl*DG_O@~t*z2wUh48r?5)hUX658488dR`PMbbk$;qCc zHCvgJHEUXiC0EIrF=e`CT9z_*cGfJ#Is+l1oJtfGR9<1xHd8}O3F%sz3gOblop~mE z2D{s7R{GCREXSXC=lFQ%EQJ*=@)QJ+v6Zeev%ADmS}O8_70ND&qMUY^S(|7^afWL- zYb1Q>wkOda^Z?#+lv671F1y`}ja9#Xje>TFl|mjp3_?FKPIjiq^GY)nzj!+Ef8%Yq?!a|BuC>bk zyH?}6SJ}V5R@r~|jR*t3)`x2it_EB;D_b`yTc1JbQCxmy|GM?KAh`}4>(=3VRoQ>b z%eY=axCW5!UqkplT##A!9IhvE-JtCE;okR{@-EnX86&>ZIrL9fHOr@ zN;+EvrKoE=r=TD6qKu9 ztWk-HD;O9_U;=tX_8%G1fBv+1JN;AUSY}Phnll@iY+N&x5rxk35$+OR8d)-hYT49L zOB0lyBB#5e)V373&bHB6?nI*lhoLbpb}m9&JlK|!nwpZN3?5lDDsj|EB|k4~+N7*Z zW!|*>%&eTOIa&GhvZrUxn5X1V&zh4DEUR?#COc!s^sboP>`Z0uoXIKqv**mpo<2pX zsB}1roE{5?an3@o$Kghcs-RRR4o@9Es<#qf>Q#6Ikb_$;$DfhoM_B>rBJa$oM!*6{ zS0%!e0O|4|451lxEkQT~5ZC|Lg$540_4=0r( zoWt&!hNKb-6T>uIqsKGkC{i?<3*=G|^cmOST8s;PGsg5(mi~jNxD`2Pp)uQu;?K{Z}HC%sn%Pff?i!q>qDS4#sdWnu8>O88bN> z2gw|a;b1feNdPluaW)Q;IT*viXbzG9W>8f@q#Pu3FouKC46wW#Dar?+yl453d&tl_I!O1Xn8KN)=qGf-6;UrE;!OqAlQDqY^n6 zATO7Rf{Qg3qmV$-3CK(0NWq0XV|ejbaLMvIT5ydPT&(nuN)lX2LRXUDN+Pblia{A{ zw~tC1S)>e3O-UU)(xwb98k?GmR$_2ca!PVaiZZxxN(UZuZeM`sJAm@~2=@V81MwW3aB~2g0O_K884iY0HW(+v2+Kias3cL6vQs$i?K9Z5wr7Jo*UR{ZgvI$7xMFp8rWT3^-C@f|K{ z^PK8a;Y)zOI*3L*7YVv6h)>EAq&O(v^KtzM8|cUnvW4OuNO9u60ryg7JM@1H+`pHd z!Vzsgu7BX7@XNTa#4F-}eq zuG{fG(SNJ!6%qfUx4G=SfO+q9blifAbbTctg)b8N7XY>g;bojM{*$2Z!iCMc>_^bd zgW_-d79L1Q=dgVu!J4|^7IxowLpq0*9AeWbyH|W1rr*k8O}fN48q~RJZ}smoO3seI z5o(UQM%}IUQv0et^&Yi{I$6C)EmlL-bJW4=Xw|4rSBI;&s86fq>O1OBY9F<~da3GH z|Dldo|DwL4MydnVY3f3?LA_snTm4+^sh+1!QRk@_t7FwgYL#kIFHmQw`RWMuR&}E~ zQFW;gs_&{R)i2d8>H)RW1xl{ELA9!NYNHyfu2R>klhio1Ks{R>gs&T2u0E=^sQuK3 z)J!5x^S2M!&wa z0=^3P1K^agsL|MP-wYTH_zhqJU}Q>12c0%l0hkNe09XV#FcsrCz&gM+faY->9UB0Z z%b*YNg^3*<5!mhiMLP5V&a^-d@KwOIfWJ)Y==cnD-Q$a0nV8W zy@1t#PXPV}@KwOEQ#v}D0j~l42JjGI6xy1%tz^tjLe}K>CKtJF~z_?zD za_MxW1Mtq79Ub=p-aiX_SOIfqcXWIJcoSed;B{9Z-FQ~=IA9*&=YaHdrF0(B3HS)$ ztALI?_!02BE0NAVin1MZxgx-;u7>{ss{q#nwgEl~SeOs{0Urkp0DcdsL?}vMysj|- zFdi@wa4et|FdMJ{FxCNiKri4ofNKCx01ijLn-i%h<1nUL4@hIH&44d0hhC$iJas+N z0XS?W(hK-!>J_xuMa5LZ! zfH(V)Z|DY&0pe2b;O~H&089UlatZhd;4gqb0Y=0qiv3A!ga_R8 zG|DgF=YUTF{kNEJdx3Qu{~r z3}1`hh42G#wI`xKB#z9U$XV23TuHc&o(~&P=M-bqWMk~*rao2SHOj<(sh5n7yAW6= zzW}iDVj5AL`jK7_uFb%=ki7U2&l+4cBjKYU+y;bq0p}0GJ%{kbQ5b^;;dUYX9B`zc zex&a+T!b4!2>b}wj_Xa}?1aFNaC87r>}bqq7>46wfg1$e+92Fe;O+-*Q4lU2xOU*~ z6F4j29N?;xkUknvPBKQ_8ZyZkdrRmfqq!k$k}+Y8!D39jv4_Q&Qrk1rSYYpMOaa1T zOaKEIAu!1pZb7>-0OA{7f^>-6fdf#I+W>q9$~7(6K5?E~PhARS^-T z=Sk3dq+t#y;^`(m=^;Oyo**-11CR|sb_K#p7U|72#)g#kHbz;PHxq%5Kz>cStfS*u z;z2rs(+pRItm(}h#gmQ z+p+uYciyEs6pQhKExn^-59z!H)g;=^zwQrbS;N2FUAOE zLRYZAL-W+$GL=*p$R|&N|Je-m6$n#ZxRJ_aZD^*^jC`aL$+MF2(zxe6@D3y0U3f$L z3eAk0(iDcV%=;LlTmwJ;Y5C0!of-NE<(I}!2d8v&;6N`?UqgFP`K7W_3fkw^j*fpK zOl1%TOfk0Ln0nl<2F;V*(J_{2R%6t3QOU`^M?t$4v|mt$sVw|)dlI@kL??qOo_6RO zi*eGW2=n&DN!6IyKx4VdplNL&-_!Wc2HLGc?v(FCt_gNN#W4rG+q=<2v3*@(M+KYma zhfi-W$j(@dJM(7YnFGSi&U|hs)eRcMz6ToXFOXMr;6bX#IlzAmyoY4)Gvg`%ZtQHV zGZ6xLF8qdOmzP%3R|lS(!IMcmHe*yF^b!6(;9rpV5@XbSQI4Jien0Rdh#&FiiLydA z(75?m&?1P2AL*en^pC({2*vobh5i%3f2YAyd5cDY_zL)Uh2Q?Lyj6t$8mf~ANzhdQ zT}63VbECMs&x6DeYu7%AY+esJALKABVm42Q%~VD<0lymfhcV9k)4IA=4fUb2>vXTI z?@}-i*tZz|`tvfEY0M2N)YC?Nbq;jhTZ*w7!n}_Oxm9G1LU~&X`bA~ybP~)?1P=XemmvY`2cLtUXUfqYP(0oHG(F_T)64Y&u+zN9 z>B6{>YzS@_3qr0imRO7x7Gt%=Sfg5utJO)ywQ7;EAY@8V1rNDOhYRW`e{wNT@>O6> z7fm%eW5 zPljU7{Ve9zs}LqzgU2gq9IiL!O)?fNFy_wDj1$RkC6GM=+2A%f0r(o=4+2lNcd3u& zF4c~;<4=Nj;EiJZLwT5C3@5v`13we^nN&w$S2z7!Zm1nKLo?DRSz13RUJbj%*ryxW z^w9fHmo-C_)*L3_8&GbZ=jkCov3#9mv`!{(q{o7%06hLRm?tAl#;uL!i=pk>~Iw&y=-D=I>6 z)5&d|6m6`Yj5csG+Q7+Z11B5Tqb=N^I*k>qJSU-HaIVEOdBn-v`D>XKNx01g?H15V z$cEtdH8*sTYRt133yO@nB|$X{Y?Llqk9qy}j*bfv<~kRkW+6Se9tCYXXkU<@PK_Zl zVPeQzL8`72_S%WC05=vGe>H9(M*S150{9$qq7~mGQOUT@7A21}%qi zRDYfT{;$AKB^^}uG5e&r8iC&jyq6M*_N!~1Ob;p3)J1A@et}GXtVK=xPs+(AH8ex7 zQ-LnZyG*QQ(KqggAjeKZG74a@3 z-pO>kiHPXkG|eIF`=cfpBeg(Kr02c~e<9|{Q)~X@TpdHLYJ6MWlC3L+i?&jkzySrfr^IZW7NIuq48z@rL zTQ-_CPcbG;F(y_Uqsm3mV`X^_cpJfMBVMdygiJMtQ<;1e_+D6pok4i$SSp05>^0)g z1kgqit;85ba~n$UUf{0)z8~T7BidH_A!!THL0Dm>ryY9!+tPLeG z(wzkUJN_y32%HtT4ZyMXN#GU$_c3s@aZf)g9~Hozi*@5?G5-9M^1;R@4ZU@GDN=|Z znVgP`hC5TH&sC6>jfA7(N57C7ey{Rd1s_UabW8)-FGSo+I>}NR>7Mk_k91KHIrBT? zfioUBmeY!`Cs_wSGJh0 zD7{5Gi~^o5;JE_!6YzWiFBC9dzytw@3YaKhl7J}!P7p9%z)S(H0_F&qE8rXf^8{QV zV1a-|0+t9^DjKutmrX7x!ZXyiCB!0!|n33IVScuvkDA>6t%gJaEPXXFPDm17|#N z#smLXJ-~Z?EG1)a0R7~BiRRv;xv$aO-=VpGRNO~!wOAv?+xot|sZ4@=wZ_v=aim?1?hfYG`b{qnHpO+Q*rreA@e(=r_W4hZ}3 zn3Vl!-I0DV9CZGb>uTwXxqWh7ELS9i)*0z1)G6ojTV*vZh~p>m0g(`C_lAL-F4yH` z{O5CYXZn5$C0XGP^Nx(({ zn+0qUuw6i9mWW?Kvw(>LrVE%WV1a-Y0@et)R=^DcZW6Fjz-9qk1Z)>j5sS@H0-6O( z6fj-DTmcINtPrq9z_kKy5O9-#jRH0c*dk!NfNawSTo5&f`@t+=qJZfF<_cIKV17LuCIK4-Y!e+=IA`_{hm#ylZ9k>a)FYPBN06dirpI`ojepE-lw68-)Ued$pgLU{xe?2G+k@5EubZMWK9;qWQ^+oH*OL|`& zd1;?nM_$t7H1dBJhD!PnL6_s`e`x5#H1q}yedM3eFC4&ABK`A}Mm||1|Fnjls-bVx z(9<;ZO&YpILw`m?&(hGJ)zD@8C;jtJ4Lw^U|D1*{+czoyyoNqaBmaViF553D|DuLI zQzO4wLzn%7lz&M>pR19dCFrvJT`B0Y{!6-?Hx=m6o{Gib<>C!(x>(Hftj_c5+e^k-_%K9tyOaFxH(4~J&I&|ru zb9Lx4{o3)Bj9=2V<16z?ZmDd425R(23jay}4AP-X{j&d&>9=eAuN_}#>Duv$q)Y#5 z#~)g{cKjjf(mw6@Lrd3=KlrSH?>nP3X~!R0x_0~_>C(U2@rRbK9e+r=v|l^^(9*Tz zk8t6CnIG|*{F8Kmzjl7` zy3jAnN3ur0q)Yv&I&_&o={odu5y2E4x-5Uwb?9axKUYKFqOpI04qe(`phK7TFVdk) z`^$Cc(tfWFUE04~LvPgBU#mlx_TQvKm-gSHLznj7sY93c-={;D_CKPbH)-tuyAEC2 z|FjNW+W))`UE2Su4qe*6O+(+RvF|M%y0ouZhc4~gt3#Lewdl~LeE|)9o5sF&9lEsd zunt|?cT$Hg?du`-xhUc%io%-*Y;2Y2T|lbZOsq9lEq{w}$?v#=Z}9 z=+eG@I&^8@ejU2B?|U7(wC|9H{+7l*ZU6l*4PDzm?$*$?{mW+Z@Jh zhUdEKm+~_GlZAhz-y~h8KcpKv<&l(={4y50CmW=kmQL}|TCCP?$w!d%k9!|~@YBxE zNIsZ;T+_bF_@%sd{zdX+pQNAA$WO_@7nKsG;6o!Wmw9B;@KM7@=2zfL9Z4fcjT&jL z7&}BD%@W6~)c?cNb5P{QDL&c<6rx}i$H26WF zlb!`ak6-X#!u0f03ZCJ5j?!td_>I6t{ND+FAH(wZuL)mK#1S&yNrJyl;D-qQ=>q?h zzzcN>m21)y<=~8BTaj{M1ji2(__ab$KA?(SRT~<(ohbrO`bf`ff`5^~mkWGcF-NfX zixFHR@QniB63Xc}0g`^R@IxX;D|8MQ>ECb-N4&tJSIB>azoL}m*|}y2-YM`a$~fN5 zf${*Lid|#T;(0xlP5M28i}V~6{EHbD?>!6r0!j#eDI(sN5hgnupXP`>@xWp`@T6y* zi?|hqol6GzE-u19UO`wzkjnN+fxjAf$`?iC%M$`$BKR}vIAW{7Q!ya^Z=ON`KUp3;g5OZj8PDZpWvSo~ zX!utO{-L*X{)eeyz^`85-~I=`XXo4?cq`M>SBVnk5XGMT)-k+EIeIYy_{nkUy+Y4B zk1;gMCkj2p`1>g>8hhx(UrJX?1J|RNx!fk)lK*e~E5B!F3nBQbz)$--$JYq4cL7;D zCM%Y}e+d|-oZe1;4Ls?W{g{cXQ`(pwz%1|w1ph|S@5KxJuLA#p2LGGD9~1Z( z!B6J^Q+f-8ep!Bc0Z;bG_i3d5Xn~jS)1bLyzkUKg@p*pVSK!S8pC|C|3H(stsXf`S zhNr7V;LkzEQM__IA({haEaOM}ChU~$=RN3Pi2t4!xPlU)XM%`VeUT$%{hcK6@dAGz zk6y9$CVS4sxnKez3J{%LLHe6Eb3JnV;T&O4%^}WzRKz<^@Spt>N3ipD5L_(ye-I6o ztS^-U-!Aa7+%6ILfQJ8ip?~!2Tt7R12f^C}{=ID+zlsCpK7p6-OPww7j|jYDJ4fUR z{6>L)P2gp_^s>Nz`7h3xF8JRR_+D>t1UnlC!S{iu{54l_{}c%RuLb{AJ2`@#hlAix zjK80BTD-v?yp%YgQRz{~dr*%>mx4`bz_uhMcePf(*s*K6Qs z?MVekn9*d?uSw|1Wrub`o}-nw1^#-0KTGKOP~dO*j5D$`Vi5d<>BqQ5_+Qr3uLS-H z4L%_78#MTX43BY3zOYlo`ws9_o|{{Eyu&$AjtTrpftTYzoXF0~NsC5*Z{SJKyI^dSt%L7WmZ~{FMS~VshPWIS91G%6doxw^ckjnQNB&}!2mfaHM-hII{C@%e*~niz z4QTPZk^|*2h8H(MV51JcQ3t%HSxDC!P5z!Q@XexL%Y2O2 z(UZdP7+1IRIE*5y41vGh!x5r-E7NrJ6bgPhkIxi(%60fx>ELexp3=2WldiSMN6Igy zkSo4O=wGj+=N}9osf-sBFInH86Z-dR{PVhwo@O2VejWTTI{2Pw2fE8gvkrb3@Me+5 zU@$?4|8jkz{tp>GQfXboHM6r~5&T?-zg_U} zdz|w>DGZ|TLUiY!UOIjopo6C~dArjyh2bL=xlSp0W()k!qMkO2_H&_*o)tRyTZNvB zYj}d#8O;dZrNd9(xsWyp7clUG4t|FY{v#dy*E;yaI{0ujXlG$uSJ2Mec{H2scQ)|U zK4faj+W>|aOhF()2R~K^pQ(et5_ofvUK#W<{zxTOv_FFd;d+6;@^2jRi@@KgqyH~D z_>DUFcYr^O*8QTo`sZ`TAE{Id|H$dtcRG6L`y^Dq)@$llPu330RGxz8>fi_K;FEOl zm+9cA15f#0A@UvOvEOVR{$k+Gf+rZ%F#bs8_{|(E*BfsJJ__m8juY+{{6&KQE}?8> z6fbXAJo>#x;3fLKi2rky)uLhpVbBY)@ z%KBa}^lZM3>scu5xmicgounrO<6*fTCHNoG;eSR4|F#bP3myCc9eh}y?(^k59sET) z_*CF2UGn`ekt}7h4*!)5AE~5^e6$IHLLL4x!N2`l&M1~g6*>>PyMDo^gTGS;|G3b9 zo}cR%!%bx)@KoQcHTC^@9X&gB@Sp17f6~E+M0B67H!(2oF1-;t{1-5Mq;lb}JY8ZM ztz07T|NNQb8#qwL08j14YE65aF8DtaLUfmpMjd=V9elD5 z-pcTiihK?YGuZD69sX+s|3jia&Jg^|b@*@7!EeyP|5FD~XK;7thuu2(eLDDm>)?;; z;3IYFky!^nOb0(+2R{{fsz;5Q{&$WJ|03Y2+^!ev6~g^WsSbZN!=oQu$vMXgff|92 zsOySft)u5Jf`9P=&i|Uw^EZJ{5aWD_|EG?gojUl>bnpj(r*>7=Q#p+|3zZFI=Pnf%5qD`m{55r(e$$;1pl?q zaeg^}A0zO~M7zz-1qMsH!1op%_-bKNw!o)}`tnbKpC<5fzl|(=S1`O_3IZj-lb@3` zey-Nx_v_&A)4@NYgWsrwe?L?QUD;D4))%#VJc{rOqOo z$3Zt`&T?Nf|N`=H!$&Dg z@OPx*sj76s+(K_Lygz&l1%@Xp5M}sETb1H++w+;j@)1jgtK1E4=BxZ7=OU-aJv>RN zM&_1#%Sv7L#Yz=2)v*Ku_*?0!QmUOe=>bu@mMYa%$`YsBS?DZv!ZdGr8SAZ(-R|O2LE|6%V&ZDR$YtZth`nXJHxiz|Dn~sBg$eVy&>bkv4m&vx0{TE0EQ0Ww8Jbc*I*y z8NgDGO6aDPReH<0)n!EtI$aE~Ld*+VrLxTBDzg=yatT8~9%Y$G3Mq9Ip)`^o9A$+L zu8PG&D38+v-=ZA3l|mQGG!(V!q6%1Kr{Ysxq*S9!F`~_5Q}Xk1Tzh_&HGlFf%e1U~ zYvwE^e@f1bNtT@a8Ivc^&YF`y$1*7=D__aaymGo_T6Ts~P1T*X2YDHl4&*U#lkFL( z-LoAYOHmPpfXKCZ&{ULXilUsK?_TO&l8^dUZYxcnl9N3tBYzaO=_M;g-ilFO5JK%_ z6q*cgr5g=T-efdmodshNS`lXvN<^8X41Vb%TaA)e>7nvV+2pA9u)Hm2rLMRO=i;DN z16t5}bs^^!dzb;GRMgQnx+`pUhdU@;UNlyAD&Z85-Gh9f_9Nd`xd;W4q`Ihcy6m2= z$+i`uKz89la_l9QT~JVDUrg<*$60nt;mEN;cA$jj=PxRAmFKgh=jSVVIWE^?Z^dj6 zTI)r!6y#Ss%blK6a>%GmmzV0HquOC#Qqd(^Bm|A?Dds5-k6@-sPnCj|NM6?Ri=i33 zLZYFe04W7XD=La)6-X$Na=FVMi?Wwh?visvVZ2`N zu9aQevc0#vQi?Rxhz`0)6tqc4Nx=_5RH;yEs#HM9_q~}n>v1AL!qV;7<9Rc0-n{p{ z&ugzQ2oH<=BCVQJv5#Q4je`*Voo6`!dyFtelO4klzBEO@vA+&TLa>3a9kNS4ML5O} zy?QuUldilfS$8Znlb{5H#9DYSXEs6WYB(D1!RioWO=3WDDD$|V)$4dU%x6V_72vF+ zbes8WW_{Jn21c-9U;+askN`Pkv@rNWM9A>)e!K+Euuo(iPhFZUhcm%gCu_uZr9MBI zBYc52Nr6BP?-qr%ANG-8(Ypr99U$dVR+r0oCj)HOogt#(*(jQ(I{&lL;j+lnGeKlb zQJe&eVUpa%>@|T?q8W7b6~FtB_Kb#i;GNSfkBIaxHPvE}^O5xe@P>e^y@{91@?0|- z?0hs#U=KomILMY*v|AT=&KJol-G>B$1lB8e2e5b$9zn$>b>6jZKunNQk>@B!1u{@sy^+b`(?yAS&6*=%&@nSDRW z7JVkou1upz)b{Obtl!3rCt+Tmsw zCRaT})7yxvvx0lh{12h)E0ghBU~ST>Dtz!mWmEW|jfE9*aBAFxyr67$uJ3EtxmT-7G+Q!_!@!h&#DNac7f5NpzS`jpIq**116>8envkEFMO)INPu8 zb!wQbd<~moj`$E1LzkOmlTDk2HwT)sEabYz5cZ z?_7sqJRB()Ajop?@^Bo(WyDW{@P2GE#0gkZI%ViMYl#zyIq3jh7>ADGayFH|2zPLT zRCB;;;t>Gg(@F!KL3fL!sHyM4>58O}yvy8B~CMaxxx(Rsrn>|UW9mMkJ2wEb7Z33FulEd4k~ zYA)jnrnV>pqSHmVC}nO@nqc*&`yqE2$3T28vq@rNELz3cK0pC>TdON*lZk)GA@pcY zT*;FO2h%LY#Sq*ZH6+YQxrfV-uJ7@_LMSnj9H)E2X5x?_>ltW}3`r%q-sct>2LgB2 z8^>@eGEZt7M8YV9dnK04n>936X?Z}~vP?+mUM}bPZ3c0cb21mQCWMaDVwE=4E(rSd zZpE)J`fyrPE_6@gx(xdBeBEEfz_R{ym!r8U(W#sPO|3~#+XoTO;fgP&fZZ#XP0*M5 z^s(EYBa6sRjpW{M5L!WBxNyHJWL^8|BAQhoE)n24a7RHOSEi|qRVx2ARR=eRSDJWk z2EnC71r{e=>OQ-rRv1BM5U(-}Z3Y7r2mPd2tw@gk@5c8h<10z`c;KZsOpjW=*BO9q z+Z&&=_yg|@wvWdI`d(!%KeUZ)x9;~1+Q${_;MSlUU*Ef|T z*45wCn&%*SPUZEz&syqxpS$+c`6ssTo3=xJkF=Kh-f7BHrfa_y>Sz-@W@WaqU(!F? z8+C1O3mmlNdsbe{mpY&o!q=&?Y00li+wyNeu;;ZjhwfA5w0zW&?^!u5U;NAXUv=d5 zc}FcTfA5Btog4n4{2r~Xzdlc=rFCB?)wSO@I74||e|?Tf%O{-x?75!I&UYx<^7{O) zmbW_tb`FCdI`S{CvN!x6a9jTLt{l3LjyZ#8ID<#Woc>P-MqbOYlQcVB`JX%T!LgCo zve%LC>i;Xw;})BXPkk@^FC)`WcRW4T@;Auh7LJpDZhx2e{6EV74q1+*@u|P-dtv2! z1h>}KYwdIQPy8|VRNg&L{lMsd!#ak}TlH4|=Y5Cv`1+jojW3M+Qx!xzZYr-Oh1>G( zd8{vu{NwkHoPPFn$ChuR8T(TG_5Ryq`vaCsGhd+R9|mvP@vnC3RsQ2nz0u0(%u_se zvD0e%qaFX-4@@Pe@iqQ*-in{NL*#TfEzRfni5clK?%T9C9bfBsTIa5HEFV2HE#K(~ IbPj{R0SAo(ssI20 literal 0 HcmV?d00001 diff --git a/st/st.1 b/st/st.1 new file mode 100755 index 0000000..39120b4 --- /dev/null +++ b/st/st.1 @@ -0,0 +1,177 @@ +.TH ST 1 st\-VERSION +.SH NAME +st \- simple terminal +.SH SYNOPSIS +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-l +.IR line ] +.RB [ \-w +.IR windowid ] +.RB [[ \-e ] +.IR command +.RI [ arguments ...]] +.PP +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-w +.IR windowid ] +.RB \-l +.IR line +.RI [ stty_args ...] +.SH DESCRIPTION +.B st +is a simple terminal emulator. +.SH OPTIONS +.TP +.B \-a +disable alternate screens in terminal +.TP +.BI \-c " class" +defines the window class (default $TERM). +.TP +.BI \-f " font" +defines the +.I font +to use when st is run. +.TP +.BI \-g " geometry" +defines the X11 geometry string. +The form is [=][{xX}][{+-}{+-}]. See +.BR XParseGeometry (3) +for further details. +.TP +.B \-i +will fixate the position given with the -g option. +.TP +.BI \-n " name" +defines the window instance name (default $TERM). +.TP +.BI \-o " iofile" +writes all the I/O to +.I iofile. +This feature is useful when recording st sessions. A value of "-" means +standard output. +.TP +.BI \-T " title" +defines the window title (default 'st'). +.TP +.BI \-t " title" +defines the window title (default 'st'). +.TP +.BI \-w " windowid" +embeds st within the window identified by +.I windowid +.TP +.BI \-l " line" +use a tty +.I line +instead of a pseudo terminal. +.I line +should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port +0). +When this flag is given +remaining arguments are used as flags for +.BR stty(1). +By default st initializes the serial line to 8 bits, no parity, 1 stop bit +and a 38400 baud rate. The speed is set by appending it as last argument +(e.g. 'st -l /dev/ttyS0 115200'). Arguments before the last one are +.BR stty(1) +flags. If you want to set odd parity on 115200 baud use for example 'st -l +/dev/ttyS0 parenb parodd 115200'. Set the number of bits by using for +example 'st -l /dev/ttyS0 cs7 115200'. See +.BR stty(1) +for more arguments and cases. +.TP +.B \-v +prints version information to stderr, then exits. +.TP +.BI \-e " command " [ " arguments " "... ]" +st executes +.I command +instead of the shell. If this is used it +.B must be the last option +on the command line, as in xterm / rxvt. +This option is only intended for compatibility, +and all the remaining arguments are used as a command +even without it. +.SH SHORTCUTS +.TP +.B Break +Send a break in the serial line. +Break key is obtained in PC keyboards +pressing at the same time control and pause. +.TP +.B Ctrl-Print Screen +Toggle if st should print to the +.I iofile. +.TP +.B Shift-Print Screen +Print the full screen to the +.I iofile. +.TP +.B Print Screen +Print the selection to the +.I iofile. +.TP +.B Ctrl-Shift-Page Up +Increase font size. +.TP +.B Ctrl-Shift-Page Down +Decrease font size. +.TP +.B Ctrl-Shift-Home +Reset to default font size. +.TP +.B Ctrl-Shift-y +Paste from primary selection (middle mouse button). +.TP +.B Ctrl-Shift-c +Copy the selected text to the clipboard selection. +.TP +.B Ctrl-Shift-v +Paste from the clipboard selection. +.SH CUSTOMIZATION +.B st +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH AUTHORS +See the LICENSE file for the authors. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR tabbed (1), +.BR utmp (1), +.BR stty (1), +.BR scroll (1) +.SH BUGS +See the TODO file in the distribution. + diff --git a/st/st.c b/st/st.c new file mode 100755 index 0000000..13af9e4 --- /dev/null +++ b/st/st.c @@ -0,0 +1,2756 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ +#define HISTSIZE 2000 + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static void osc_color_response(int, int, int); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int, int); +static void tscrolldown(int, int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint((unsigned char)**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + static const char base64_digits[256] = { + [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, + 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup(void) +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (term.scr == 0) + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (term.scr == 0) + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + 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; + int sep = ';'; /* colon or semi-colon, but not both */ + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (sep == ';' && *p == ':') + sep = ':'; /* allow override to colon once */ + if (*p != sep || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; + + if (isboxdraw(u)) + term.line[y][x].mode |= ATTR_BOXDRAW; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + LIMIT(csiescseq.arg[0], 1, 65535); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + 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 > 0) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + if (csiescseq.priv) break; + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR -- Device Status Report */ + switch (csiescseq.arg[0]) { + case 5: /* Status Report "OK" `0n` */ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) ";R" */ + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + break; + default: + goto unknown; + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + if (csiescseq.priv) { + goto unknown; + } else { + tcursor(CURSOR_LOAD); + } + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc_color_response(int num, int index, int is_osc4) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch %s color %d\n", + is_osc4 ? "osc4" : "osc", + is_osc4 ? num : index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + is_osc4 ? "4;" : "", num, r, r, g, g, b, b); + if (n < 0 || n >= sizeof(buf)) { + fprintf(stderr, "error: %s while printing %s response\n", + n < 0 ? "snprintf failed" : "truncation occurred", + is_osc4 ? "osc4" : "osc"); + } else { + ttywrite(buf, n, 1); + } +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + const struct { int idx; char *str; } osc_table[] = { + { defaultfg, "foreground" }, + { defaultbg, "background" }, + { defaultcs, "cursor" } + }; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1]); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: + case 11: + case 12: + if (narg < 2) + break; + p = strescseq.args[1]; + if ((j = par - 10) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + + if (!strcmp(p, "?")) { + osc_color_response(par, osc_table[j].idx, 0); + } else if (xsetcolorname(osc_table[j].idx, p)) { + fprintf(stderr, "erresc: invalid %s color: %s\n", + osc_table[j].str, p); + } else { + tfulldirt(); + } + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) { + osc_color_response(j, 0, 1); + } else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) { + xloadcols(); + return; /* color reset without parameter */ + } + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + xsetmode(0, MODE_HIDE); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + /* in UTF-8 mode ignore handling C1 control characters */ + if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) + return; + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + 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)); + gp->mode &= ~ATTR_WIDE; + } + + if (term.c.x+width > term.col) { + if (IS_SET(MODE_WRAP)) + tnewline(1); + else + tmoveto(term.col - width, term.c.y); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(TLINE(y), x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st/st.c.orig b/st/st.c.orig new file mode 100755 index 0000000..7dcfb4c --- /dev/null +++ b/st/st.c.orig @@ -0,0 +1,2685 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static void osc_color_response(int, int, int); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint((unsigned char)**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + static const char base64_digits[256] = { + [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, + 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(term.line[*y-1][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(term.line[*y][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &term.line[y][MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup(void) +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + int sep = ';'; /* colon or semi-colon, but not both */ + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (sep == ';' && *p == ':') + sep = ':'; /* allow override to colon once */ + if (*p != sep || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; + + if (isboxdraw(u)) + term.line[y][x].mode |= ATTR_BOXDRAW; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + LIMIT(csiescseq.arg[0], 1, 65535); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + 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 > 0) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + if (csiescseq.priv) break; + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR -- Device Status Report */ + switch (csiescseq.arg[0]) { + case 5: /* Status Report "OK" `0n` */ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) ";R" */ + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + break; + default: + goto unknown; + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + if (csiescseq.priv) { + goto unknown; + } else { + tcursor(CURSOR_LOAD); + } + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc_color_response(int num, int index, int is_osc4) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch %s color %d\n", + is_osc4 ? "osc4" : "osc", + is_osc4 ? num : index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + is_osc4 ? "4;" : "", num, r, r, g, g, b, b); + if (n < 0 || n >= sizeof(buf)) { + fprintf(stderr, "error: %s while printing %s response\n", + n < 0 ? "snprintf failed" : "truncation occurred", + is_osc4 ? "osc4" : "osc"); + } else { + ttywrite(buf, n, 1); + } +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + const struct { int idx; char *str; } osc_table[] = { + { defaultfg, "foreground" }, + { defaultbg, "background" }, + { defaultcs, "cursor" } + }; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1]); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: + case 11: + case 12: + if (narg < 2) + break; + p = strescseq.args[1]; + if ((j = par - 10) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + + if (!strcmp(p, "?")) { + osc_color_response(par, osc_table[j].idx, 0); + } else if (xsetcolorname(osc_table[j].idx, p)) { + fprintf(stderr, "erresc: invalid %s color: %s\n", + osc_table[j].str, p); + } else { + tfulldirt(); + } + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) { + osc_color_response(j, 0, 1); + } else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) { + xloadcols(); + return; /* color reset without parameter */ + } + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + xsetmode(0, MODE_HIDE); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + /* in UTF-8 mode ignore handling C1 control characters */ + if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) + return; + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + 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)); + gp->mode &= ~ATTR_WIDE; + } + + if (term.c.x+width > term.col) { + if (IS_SET(MODE_WRAP)) + tnewline(1); + else + tmoveto(term.col - width, term.c.y); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st/st.h b/st/st.h new file mode 100755 index 0000000..9870e80 --- /dev/null +++ b/st/st.h @@ -0,0 +1,144 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOXDRAW = 1 << 11, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum drawing_mode { + DRAW_NONE = 0, + DRAW_BG = 1 << 0, + DRAW_FG = 1 << 1, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +int isboxdraw(Rune); +ushort boxdrawindex(const Glyph *); +#ifdef XFT_VERSION +/* only exposed to x.c, otherwise we'll need Xft.h for the types */ +void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *); +void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int); +#endif + +/* 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 const int boxdraw, boxdraw_bold, boxdraw_braille; diff --git a/st/st.h.orig b/st/st.h.orig new file mode 100755 index 0000000..808f5f7 --- /dev/null +++ b/st/st.h.orig @@ -0,0 +1,136 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOXDRAW = 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 printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +int isboxdraw(Rune); +ushort boxdrawindex(const Glyph *); +#ifdef XFT_VERSION +/* only exposed to x.c, otherwise we'll need Xft.h for the types */ +void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *); +void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int); +#endif + +/* 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 const int boxdraw, boxdraw_bold, boxdraw_braille; diff --git a/st/st.info b/st/st.info new file mode 100755 index 0000000..efab2cf --- /dev/null +++ b/st/st.info @@ -0,0 +1,243 @@ +st-mono| simpleterm monocolor, + acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, + am, + bce, + bel=^G, + blink=\E[5m, + bold=\E[1m, + cbt=\E[Z, + cvvis=\E[?25h, + civis=\E[?25l, + clear=\E[H\E[2J, + cnorm=\E[?12l\E[?25h, + colors#2, + cols#80, + cr=^M, + csr=\E[%i%p1%d;%p2%dr, + cub=\E[%p1%dD, + cub1=^H, + cud1=^J, + cud=\E[%p1%dB, + cuf1=\E[C, + cuf=\E[%p1%dC, + cup=\E[%i%p1%d;%p2%dH, + cuu1=\E[A, + cuu=\E[%p1%dA, + dch=\E[%p1%dP, + dch1=\E[P, + dim=\E[2m, + dl=\E[%p1%dM, + dl1=\E[M, + ech=\E[%p1%dX, + ed=\E[J, + el=\E[K, + el1=\E[1K, + enacs=\E)0, + flash=\E[?5h$<80/>\E[?5l, + fsl=^G, + home=\E[H, + hpa=\E[%i%p1%dG, + hs, + ht=^I, + hts=\EH, + ich=\E[%p1%d@, + il1=\E[L, + il=\E[%p1%dL, + ind=^J, + indn=\E[%p1%dS, + invis=\E[8m, + is2=\E[4l\E>\E[?1034l, + it#8, + kel=\E[1;2F, + ked=\E[1;5F, + ka1=\E[1~, + ka3=\E[5~, + kc1=\E[4~, + kc3=\E[6~, + kbs=\177, + kcbt=\E[Z, + kb2=\EOu, + kcub1=\EOD, + kcud1=\EOB, + kcuf1=\EOC, + kcuu1=\EOA, + kDC=\E[3;2~, + kent=\EOM, + kEND=\E[1;2F, + kIC=\E[2;2~, + kNXT=\E[6;2~, + kPRV=\E[5;2~, + kHOM=\E[1;2H, + kLFT=\E[1;2D, + kRIT=\E[1;2C, + kind=\E[1;2B, + kri=\E[1;2A, + kclr=\E[3;5~, + kdl1=\E[3;2~, + kdch1=\E[3~, + kich1=\E[2~, + kend=\E[4~, + kf1=\EOP, + kf2=\EOQ, + kf3=\EOR, + kf4=\EOS, + kf5=\E[15~, + kf6=\E[17~, + kf7=\E[18~, + kf8=\E[19~, + kf9=\E[20~, + kf10=\E[21~, + kf11=\E[23~, + kf12=\E[24~, + kf13=\E[1;2P, + kf14=\E[1;2Q, + kf15=\E[1;2R, + kf16=\E[1;2S, + kf17=\E[15;2~, + kf18=\E[17;2~, + kf19=\E[18;2~, + kf20=\E[19;2~, + kf21=\E[20;2~, + kf22=\E[21;2~, + kf23=\E[23;2~, + kf24=\E[24;2~, + kf25=\E[1;5P, + kf26=\E[1;5Q, + kf27=\E[1;5R, + kf28=\E[1;5S, + kf29=\E[15;5~, + kf30=\E[17;5~, + kf31=\E[18;5~, + kf32=\E[19;5~, + kf33=\E[20;5~, + kf34=\E[21;5~, + kf35=\E[23;5~, + kf36=\E[24;5~, + kf37=\E[1;6P, + kf38=\E[1;6Q, + kf39=\E[1;6R, + kf40=\E[1;6S, + kf41=\E[15;6~, + kf42=\E[17;6~, + kf43=\E[18;6~, + kf44=\E[19;6~, + kf45=\E[20;6~, + kf46=\E[21;6~, + kf47=\E[23;6~, + kf48=\E[24;6~, + kf49=\E[1;3P, + kf50=\E[1;3Q, + kf51=\E[1;3R, + kf52=\E[1;3S, + kf53=\E[15;3~, + kf54=\E[17;3~, + kf55=\E[18;3~, + kf56=\E[19;3~, + kf57=\E[20;3~, + kf58=\E[21;3~, + kf59=\E[23;3~, + kf60=\E[24;3~, + kf61=\E[1;4P, + kf62=\E[1;4Q, + kf63=\E[1;4R, + khome=\E[1~, + kil1=\E[2;5~, + krmir=\E[2;2~, + knp=\E[6~, + kmous=\E[M, + kpp=\E[5~, + lines#24, + mir, + msgr, + npc, + op=\E[39;49m, + pairs#64, + mc0=\E[i, + mc4=\E[4i, + mc5=\E[5i, + rc=\E8, + rev=\E[7m, + ri=\EM, + rin=\E[%p1%dT, + ritm=\E[23m, + rmacs=\E(B, + rmcup=\E[?1049l, + rmir=\E[4l, + rmkx=\E[?1l\E>, + rmso=\E[27m, + rmul=\E[24m, + rs1=\Ec, + rs2=\E[4l\E>\E[?1034l, + sc=\E7, + sitm=\E[3m, + sgr0=\E[0m, + smacs=\E(0, + smcup=\E[?1049h, + smir=\E[4h, + smkx=\E[?1h\E=, + smso=\E[7m, + smul=\E[4m, + tbc=\E[3g, + tsl=\E]0;, + xenl, + vpa=\E[%i%p1%dd, +# XTerm extensions + rmxx=\E[29m, + smxx=\E[9m, + BE=\E[?2004h, + BD=\E[?2004l, + PS=\E[200~, + PE=\E[201~, +# disabled rep for now: causes some issues with older ncurses versions. +# rep=%p1%c\E[%p2%{1}%-%db, +# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) + Tc, + Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, + +st| simpleterm, + use=st-mono, + colors#8, + setab=\E[4%p1%dm, + setaf=\E[3%p1%dm, + setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, + +st-256color| simpleterm with 256 colors, + use=st, + ccc, + colors#256, + oc=\E]104\007, + pairs#32767, +# Nicked from xterm-256color + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, + +st-meta| simpleterm with meta key, + use=st, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-meta-256color| simpleterm with meta key and 256 colors, + use=st-256color, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-bs| simpleterm with backspace as backspace, + use=st, + kbs=\010, + kdch1=\177, + +st-bs-256color| simpleterm with backspace as backspace and 256colors, + use=st-256color, + kbs=\010, + kdch1=\177, diff --git a/st/st.o b/st/st.o new file mode 100644 index 0000000000000000000000000000000000000000..f0de12462a1bdd4136dbcf1ffe14fb90c11b70b2 GIT binary patch literal 81792 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static uint buttons; /* bit field of pressed buttons */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + 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); + } + + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + if (x == defaultbg) { + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + } + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent, root; + 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); + + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + + if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) { + xw.vis = vis.visual; + xw.depth = vis.depth; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.vis = attr.visual; + xw.depth = attr.depth; + } + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + if (parent != root) + XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + 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; + + boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + if (mode & ATTR_BOXDRAW) { + /* minor shoehorning: boxdraw uses only this ushort */ + glyphidx = boxdrawindex(&glyphs[i]); + } else { + /* 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 dmode) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + if (dmode & DRAW_BG) { + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + /* Fill the background */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + } + + + if (dmode & DRAW_FG) { + if (base.mode & ATTR_BOXDRAW) { + drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); + } else { + /* 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 + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, + width, 1); + } + } +} + +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, DRAW_BG | DRAW_FG); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs, numspecs_cached; + Glyph base, new; + XftGlyphFontSpec *specs; + + numspecs_cached = xmakeglyphfontspecs(xw.specbuf, &line[x1], x2 - x1, x1, y1); + + /* Draw line in 2 passes: background and foreground. This way wide glyphs + won't get truncated (#223) */ + for (int dmode = DRAW_BG; dmode <= DRAW_FG; dmode <<= 1) { + specs = xw.specbuf; + numspecs = numspecs_cached; + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1, dmode); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1, dmode); + } +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) { + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (status == XBufferOverflow) + return; + } else { + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + } + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'A': + alpha = strtof(EARGF(usage()), NULL); + LIMIT(alpha, 0.0, 1.0); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/st/x.c.orig b/st/x.c.orig new file mode 100755 index 0000000..56acdfd --- /dev/null +++ b/st/x.c.orig @@ -0,0 +1,2145 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static uint buttons; /* bit field of pressed buttons */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + if (x == defaultbg) { + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + } + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent, root; + 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); + + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + + if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) { + xw.vis = vis.visual; + xw.depth = vis.depth; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.vis = attr.visual; + xw.depth = attr.depth; + } + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + if (parent != root) + XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + 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; + + boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + if (mode & ATTR_BOXDRAW) { + /* minor shoehorning: boxdraw uses only this ushort */ + glyphidx = boxdrawindex(&glyphs[i]); + } else { + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + } + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + if (base.mode & ATTR_BOXDRAW) { + drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); + } else { + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + } + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) { + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (status == XBufferOverflow) + return; + } else { + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + } + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'A': + alpha = strtof(EARGF(usage()), NULL); + LIMIT(alpha, 0.0, 1.0); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/st/x.c.rej b/st/x.c.rej new file mode 100755 index 0000000..85ea3b6 --- /dev/null +++ b/st/x.c.rej @@ -0,0 +1,113 @@ +--- x.c ++++ x.c +@@ -105,6 +105,7 @@ typedef struct { + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ ++ int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ + } XWindow; +@@ -752,7 +753,7 @@ xresize(int col, int row) + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + +@@ -812,6 +813,10 @@ xloadcols(void) + else + die("could not allocate color %d\n", i); + } ++ ++ dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); ++ dc.col[defaultbg].pixel &= 0x00FFFFFF; ++ dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; + } + +@@ -842,6 +847,12 @@ xsetcolorname(int x, const char *name) + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + ++ if (x == defaultbg) { ++ dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); ++ dc.col[defaultbg].pixel &= 0x00FFFFFF; ++ dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; ++ } ++ + return 0; + } + +@@ -1134,11 +1145,25 @@ xinit(int cols, int rows) + Window parent, root; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; ++ XWindowAttributes attr; ++ XVisualInfo vis; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); +- xw.vis = XDefaultVisual(xw.dpy, xw.scr); ++ ++ root = XRootWindow(xw.dpy, xw.scr); ++ if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) ++ parent = root; ++ ++ if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) { ++ xw.vis = vis.visual; ++ xw.depth = vis.depth; ++ } else { ++ XGetWindowAttributes(xw.dpy, parent, &attr); ++ xw.vis = attr.visual; ++ xw.depth = attr.depth; ++ } + + /* font */ + if (!FcInit()) +@@ -1148,7 +1173,7 @@ xinit(int cols, int rows) + xloadfonts(usedfont, 0); + + /* colors */ +- xw.cmap = XDefaultColormap(xw.dpy, xw.scr); ++ xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ +@@ -1168,11 +1193,8 @@ xinit(int cols, int rows) + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + +- root = XRootWindow(xw.dpy, xw.scr); +- if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) +- parent = root; +- xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, +- win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, ++ 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); + if (parent != root) +@@ -1183,7 +1205,7 @@ xinit(int cols, int rows) + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.depth); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + +@@ -2047,6 +2069,10 @@ main(int argc, char *argv[]) + case 'a': + allowaltscreen = 0; + break; ++ case 'A': ++ alpha = strtof(EARGF(usage()), NULL); ++ LIMIT(alpha, 0.0, 1.0); ++ break; + case 'c': + opt_class = EARGF(usage()); + break; diff --git a/st/x.o b/st/x.o new file mode 100644 index 0000000000000000000000000000000000000000..a4d88e1d5e09ebd4765d83831f6bcd07e6180416 GIT binary patch literal 77560 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 literal 0 HcmV?d00001