From 52c4915903419735dbb7a9cd90a1f156e3834ae3 Mon Sep 17 00:00:00 2001 From: CoolnsX Date: Wed, 14 Sep 2022 14:52:56 +0530 Subject: [PATCH] dmenu,st latest version with all patches.. --- dmenu-5.1/LICENSE | 30 + dmenu-5.1/Makefile | 64 + dmenu-5.1/README | 24 + dmenu-5.1/arg.h | 49 + dmenu-5.1/config.def.h | 31 + dmenu-5.1/config.def.h.orig | 28 + dmenu-5.1/config.h | 31 + dmenu-5.1/config.mk | 31 + dmenu-5.1/dmenu | Bin 0 -> 48032 bytes dmenu-5.1/dmenu.1 | 202 +++ dmenu-5.1/dmenu.1.orig | 197 +++ dmenu-5.1/dmenu.c | 936 ++++++++++++ dmenu-5.1/dmenu.c.orig | 820 +++++++++++ dmenu-5.1/dmenu.o | Bin 0 -> 37784 bytes dmenu-5.1/dmenu_path | 13 + dmenu-5.1/dmenu_run | 2 + dmenu-5.1/drw.c | 436 ++++++ dmenu-5.1/drw.h | 57 + dmenu-5.1/drw.o | Bin 0 -> 10504 bytes dmenu-5.1/stest | Bin 0 -> 21544 bytes dmenu-5.1/stest.1 | 90 ++ dmenu-5.1/stest.c | 109 ++ dmenu-5.1/stest.o | Bin 0 -> 5232 bytes dmenu-5.1/util.c | 35 + dmenu-5.1/util.h | 8 + dmenu-5.1/util.o | Bin 0 -> 2216 bytes st-0.8.5/FAQ | 250 ++++ st-0.8.5/LEGACY | 17 + st-0.8.5/LICENSE | 34 + st-0.8.5/Makefile | 58 + st-0.8.5/README | 34 + st-0.8.5/TODO | 28 + st-0.8.5/arg.h | 50 + st-0.8.5/config.def.h | 517 +++++++ st-0.8.5/config.def.h.orig | 481 ++++++ st-0.8.5/config.def.h.rej | 45 + st-0.8.5/config.h | 517 +++++++ st-0.8.5/config.mk | 37 + st-0.8.5/config.mk.orig | 37 + st-0.8.5/hb.c | 145 ++ st-0.8.5/hb.h | 6 + st-0.8.5/hb.o | Bin 0 -> 4632 bytes st-0.8.5/st | Bin 0 -> 119560 bytes st-0.8.5/st.1 | 177 +++ st-0.8.5/st.c | 2767 +++++++++++++++++++++++++++++++++++ st-0.8.5/st.c.orig | 2766 ++++++++++++++++++++++++++++++++++ st-0.8.5/st.h | 134 ++ st-0.8.5/st.info | 239 +++ st-0.8.5/st.o | Bin 0 -> 81696 bytes st-0.8.5/win.h | 40 + st-0.8.5/x.c | 2182 +++++++++++++++++++++++++++ st-0.8.5/x.c.orig | 2112 ++++++++++++++++++++++++++ st-0.8.5/x.o | Bin 0 -> 81392 bytes 53 files changed, 15866 insertions(+) create mode 100644 dmenu-5.1/LICENSE create mode 100644 dmenu-5.1/Makefile create mode 100644 dmenu-5.1/README create mode 100644 dmenu-5.1/arg.h create mode 100644 dmenu-5.1/config.def.h create mode 100644 dmenu-5.1/config.def.h.orig create mode 100644 dmenu-5.1/config.h create mode 100644 dmenu-5.1/config.mk create mode 100755 dmenu-5.1/dmenu create mode 100644 dmenu-5.1/dmenu.1 create mode 100644 dmenu-5.1/dmenu.1.orig create mode 100644 dmenu-5.1/dmenu.c create mode 100644 dmenu-5.1/dmenu.c.orig create mode 100644 dmenu-5.1/dmenu.o create mode 100755 dmenu-5.1/dmenu_path create mode 100755 dmenu-5.1/dmenu_run create mode 100644 dmenu-5.1/drw.c create mode 100644 dmenu-5.1/drw.h create mode 100644 dmenu-5.1/drw.o create mode 100755 dmenu-5.1/stest create mode 100644 dmenu-5.1/stest.1 create mode 100644 dmenu-5.1/stest.c create mode 100644 dmenu-5.1/stest.o create mode 100644 dmenu-5.1/util.c create mode 100644 dmenu-5.1/util.h create mode 100644 dmenu-5.1/util.o create mode 100644 st-0.8.5/FAQ create mode 100644 st-0.8.5/LEGACY create mode 100644 st-0.8.5/LICENSE create mode 100644 st-0.8.5/Makefile create mode 100644 st-0.8.5/README create mode 100644 st-0.8.5/TODO create mode 100644 st-0.8.5/arg.h create mode 100644 st-0.8.5/config.def.h create mode 100644 st-0.8.5/config.def.h.orig create mode 100644 st-0.8.5/config.def.h.rej create mode 100644 st-0.8.5/config.h create mode 100644 st-0.8.5/config.mk create mode 100644 st-0.8.5/config.mk.orig create mode 100644 st-0.8.5/hb.c create mode 100644 st-0.8.5/hb.h create mode 100644 st-0.8.5/hb.o create mode 100755 st-0.8.5/st create mode 100644 st-0.8.5/st.1 create mode 100644 st-0.8.5/st.c create mode 100644 st-0.8.5/st.c.orig create mode 100644 st-0.8.5/st.h create mode 100644 st-0.8.5/st.info create mode 100644 st-0.8.5/st.o create mode 100644 st-0.8.5/win.h create mode 100644 st-0.8.5/x.c create mode 100644 st-0.8.5/x.c.orig create mode 100644 st-0.8.5/x.o diff --git a/dmenu-5.1/LICENSE b/dmenu-5.1/LICENSE new file mode 100644 index 0000000..2a64b28 --- /dev/null +++ b/dmenu-5.1/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-5.1/Makefile b/dmenu-5.1/Makefile new file mode 100644 index 0000000..a03a95c --- /dev/null +++ b/dmenu-5.1/Makefile @@ -0,0 +1,64 @@ +# dmenu - dynamic menu +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dmenu.c stest.c util.c +OBJ = $(SRC:.c=.o) + +all: options dmenu stest + +options: + @echo dmenu build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + @echo "CC = $(CC)" + +.c.o: + $(CC) -c $(CFLAGS) $< + +config.h: + cp config.def.h $@ + +$(OBJ): arg.h config.h config.mk drw.h + +dmenu: dmenu.o drw.o util.o + $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) + +stest: stest.o + $(CC) -o $@ stest.o $(LDFLAGS) + +clean: + rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz + +dist: clean + mkdir -p dmenu-$(VERSION) + cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ + drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ + dmenu-$(VERSION) + tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) + gzip dmenu-$(VERSION).tar + rm -rf dmenu-$(VERSION) + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run + chmod 755 $(DESTDIR)$(PREFIX)/bin/stest + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ + $(DESTDIR)$(PREFIX)/bin/dmenu_path\ + $(DESTDIR)$(PREFIX)/bin/dmenu_run\ + $(DESTDIR)$(PREFIX)/bin/stest\ + $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ + $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +.PHONY: all options clean dist install uninstall diff --git a/dmenu-5.1/README b/dmenu-5.1/README new file mode 100644 index 0000000..a8fcdfe --- /dev/null +++ b/dmenu-5.1/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-5.1/arg.h b/dmenu-5.1/arg.h new file mode 100644 index 0000000..e94e02b --- /dev/null +++ b/dmenu-5.1/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-5.1/config.def.h b/dmenu-5.1/config.def.h new file mode 100644 index 0000000..e8b6493 --- /dev/null +++ b/dmenu-5.1/config.def.h @@ -0,0 +1,31 @@ +/* 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 */ +static int centered = 1; /* -c option; centers dmenu on screen */ +static int min_width = 500; /* minimum width when centered */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "Roboto:size=13" +}; +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", "#8800aa" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 10; +/* -h option; minimum height of a menu line */ +static unsigned int lineheight = 0; +static unsigned int min_lineheight = 8; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* Size of the window border */ +static const unsigned int border_width = 4; diff --git a/dmenu-5.1/config.def.h.orig b/dmenu-5.1/config.def.h.orig new file mode 100644 index 0000000..bd88a8b --- /dev/null +++ b/dmenu-5.1/config.def.h.orig @@ -0,0 +1,28 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +static int centered = 1; /* -c option; centers dmenu on screen */ +static int min_width = 500; /* minimum width when centered */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "monospace:size=10" +}; +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[] = " "; + +/* Size of the window border */ +static const unsigned int border_width = 5; diff --git a/dmenu-5.1/config.h b/dmenu-5.1/config.h new file mode 100644 index 0000000..e8b6493 --- /dev/null +++ b/dmenu-5.1/config.h @@ -0,0 +1,31 @@ +/* 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 */ +static int centered = 1; /* -c option; centers dmenu on screen */ +static int min_width = 500; /* minimum width when centered */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "Roboto:size=13" +}; +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", "#8800aa" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 10; +/* -h option; minimum height of a menu line */ +static unsigned int lineheight = 0; +static unsigned int min_lineheight = 8; + +/* + * Characters not considered part of a word while deleting words + * for example: " /?\"&[]" + */ +static const char worddelimiters[] = " "; + +/* Size of the window border */ +static const unsigned int border_width = 4; diff --git a/dmenu-5.1/config.mk b/dmenu-5.1/config.mk new file mode 100644 index 0000000..0df3fc8 --- /dev/null +++ b/dmenu-5.1/config.mk @@ -0,0 +1,31 @@ +# dmenu version +VERSION = 5.1 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = $(X11INC)/freetype2 + +# includes and libs +INCS = -I$(X11INC) -I$(FREETYPEINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_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-5.1/dmenu b/dmenu-5.1/dmenu new file mode 100755 index 0000000000000000000000000000000000000000..46a34965a48e251bd5dec62de7388da41ecd55f3 GIT binary patch literal 48032 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 literal 0 HcmV?d00001 diff --git a/dmenu-5.1/dmenu.1 b/dmenu-5.1/dmenu.1 new file mode 100644 index 0000000..c80cd20 --- /dev/null +++ b/dmenu-5.1/dmenu.1 @@ -0,0 +1,202 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfiv ] +.RB [ \-l +.IR lines ] +.RB [ \-h +.IR height ] +.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 \-c +dmenu appears centered on the screen. +.TP +.B \-f +dmenu grabs the keyboard before reading stdin if not reading from a tty. This +is faster, but will lock up X until stdin reaches end\-of\-file. +.TP +.B \-i +dmenu matches menu items case insensitively. +.TP +.BI \-l " lines" +dmenu lists items vertically, with the given number of lines. +.TP +.BI \-h " height" +dmenu uses a menu line of at least 'height' pixels tall, but no less than 8. +.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-5.1/dmenu.1.orig b/dmenu-5.1/dmenu.1.orig new file mode 100644 index 0000000..c036baa --- /dev/null +++ b/dmenu-5.1/dmenu.1.orig @@ -0,0 +1,197 @@ +.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 \-c +dmenu appears centered on the screen. +.TP +.B \-f +dmenu grabs the keyboard before reading stdin if not reading from a tty. This +is faster, but will lock up X until stdin reaches end\-of\-file. +.TP +.B \-i +dmenu matches menu items case insensitively. +.TP +.BI \-l " lines" +dmenu lists items vertically, with the given number of lines. +.TP +.BI \-m " monitor" +dmenu is displayed on the monitor number supplied. Monitor numbers are starting +from 0. +.TP +.BI \-p " prompt" +defines the prompt to be displayed to the left of the input field. +.TP +.BI \-fn " font" +defines the font or font set used. +.TP +.BI \-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-5.1/dmenu.c b/dmenu-5.1/dmenu.c new file mode 100644 index 0000000..aa338bc --- /dev/null +++ b/dmenu-5.1/dmenu.c @@ -0,0 +1,936 @@ +/* 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 LENGTH(X) (sizeof X / sizeof X[0]) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw; +static int lrpad; /* sum of left and right padding */ +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; + +static 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 : MIN(TEXTW(next->text), n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) + break; +} + +static int +max_textw(void) +{ + int len = 0; + for (struct item *item = items; item && item->text; item++) + len = MAX(TEXTW(item->text), len); + return len; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + 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, fh = drw->fonts->h, 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 + (bh - fh) / 2, 2, fh - 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, MIN(TEXTW(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 %u 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[32]; + int len; + KeySym ksym; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + 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(*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; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text); + match(); + break; + } + +draw: + drawmenu(); +} + +static void +buttonpress(XEvent *e) +{ + struct item *item; + XButtonPressedEvent *ev = &e->xbutton; + int x = 0, y = 0, h = bh, w; + + if (ev->window != win) + return; + + /* right-click: exit */ + if (ev->button == Button3) + exit(1); + + if (prompt && *prompt) + x += promptw; + + /* input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + + /* left-click on input: clear input, + * NOTE: if there is no left-arrow the space for < is reserved so + * add that to the input width */ + if (ev->button == Button1 && + ((lines <= 0 && ev->x >= 0 && ev->x <= x + w + + ((!prev || !curr->left) ? TEXTW("<") : 0)) || + (lines > 0 && ev->y >= y && ev->y <= y + h))) { + insert(NULL, -cursor); + drawmenu(); + return; + } + /* middle-mouse click: paste selection */ + if (ev->button == Button2) { + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + drawmenu(); + return; + } + /* scroll up */ + if (ev->button == Button4 && prev) { + sel = curr = prev; + calcoffsets(); + drawmenu(); + return; + } + /* scroll down */ + if (ev->button == Button5 && next) { + sel = curr = next; + calcoffsets(); + drawmenu(); + return; + } + if (ev->button != Button1) + return; + if (ev->state & ~ControlMask) + return; + if (lines > 0) { + /* vertical list: (ctrl)left-click on item */ + w = mw - x; + for (item = curr; item != next; item = item->right) { + y += h; + if (ev->y >= y && ev->y <= (y + h)) { + puts(item->text); + if (!(ev->state & ControlMask)) + exit(0); + sel = item; + if (sel) { + sel->out = 1; + drawmenu(); + } + return; + } + } + } else if (matches) { + /* left-click on left arrow */ + x += inputw; + w = TEXTW("<"); + if (prev && curr->left) { + if (ev->x >= x && ev->x <= x + w) { + sel = curr = prev; + calcoffsets(); + drawmenu(); + return; + } + } + /* horizontal list: (ctrl)left-click on item */ + for (item = curr; item != next; item = item->right) { + x += w; + w = MIN(TEXTW(item->text), mw - x - TEXTW(">")); + if (ev->x >= x && ev->x <= x + w) { + puts(item->text); + if (!(ev->state & ControlMask)) + exit(0); + sel = item; + if (sel) { + sel->out = 1; + drawmenu(); + } + return; + } + } + /* left-click on right arrow */ + w = TEXTW(">"); + x = mw - w; + if (next && ev->x >= x && ev->x <= x + w) { + sel = curr = next; + calcoffsets(); + drawmenu(); + return; + } + } +} + +static void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* we have been given the current selection, now insert it into input */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +static void +readstdin(void) +{ + char buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 0; + + /* read each line from stdin and add it to the item list */ + for (i = 0; fgets(buf, sizeof buf, stdin); i++) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + die("cannot realloc %u bytes:", size); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(items[i].text = strdup(buf))) + die("cannot strdup %u bytes:", strlen(buf) + 1); + items[i].out = 0; + drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); + if (tmpmax > inputw) { + inputw = tmpmax; + imax = i; + } + } + if (items) + items[i].text = NULL; + inputw = items ? TEXTW(items[imax].text) : 0; + 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 ButtonPress: + buttonpress(&ev); + break; + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) + XRaiseWindow(dpy, win); + break; + } + } +} + +static void +setup(void) +{ + int x, y, i, 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; + bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */ + lines = MAX(lines, 0); + mh = (lines + 1) * bh; + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; +#ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i])) + break; + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); + } else { + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + } + + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; + } else { + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + } + inputw = MIN(inputw, mw/3); + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + XSetClassHint(dpy, win, &ch); + + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + if (embed) { + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); +} + +static void +usage(void) +{ + fputs("usage: dmenu [-bfiv] [-l lines] [-h height] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); +} + +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], "-c")) /* centers dmenu on screen */ + centered = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ + lineheight = atoi(argv[++i]); + lineheight = MAX(lineheight, min_lineheight); + } + 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-5.1/dmenu.c.orig b/dmenu-5.1/dmenu.c.orig new file mode 100644 index 0000000..940898b --- /dev/null +++ b/dmenu-5.1/dmenu.c.orig @@ -0,0 +1,820 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include + +#include "drw.h" +#include "util.h" + +/* macros */ +#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ + * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw; +static int lrpad; /* sum of left and right padding */ +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; + +static 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 : MIN(TEXTW(next->text), n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) + break; +} + +static int +max_textw(void) +{ + int len = 0; + for (struct item *item = items; item && item->text; item++) + len = MAX(TEXTW(item->text), len); + return len; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + 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, fh = drw->fonts->h, 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 + (bh - fh) / 2, 2, fh - 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, MIN(TEXTW(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 %u 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[32]; + int len; + KeySym ksym; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + 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(*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; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text); + 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 buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + unsigned int tmpmax = 0; + + /* read each line from stdin and add it to the item list */ + for (i = 0; fgets(buf, sizeof buf, stdin); i++) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + die("cannot realloc %u bytes:", size); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(items[i].text = strdup(buf))) + die("cannot strdup %u bytes:", strlen(buf) + 1); + items[i].out = 0; + drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL); + if (tmpmax > inputw) { + inputw = tmpmax; + imax = i; + } + } + if (items) + items[i].text = NULL; + inputw = items ? TEXTW(items[imax].text) : 0; + 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; + bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */ + lines = MAX(lines, 0); + mh = (lines + 1) * bh; + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; +#ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i])) + break; + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); + } else { + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + } + + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; + } else { + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + } + inputw = MIN(inputw, mw/3); + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + XSetClassHint(dpy, win, &ch); + + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + if (embed) { + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); +} + +static void +usage(void) +{ + fputs("usage: dmenu [-bfiv] [-l lines] [-h height] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); +} + +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], "-c")) /* centers dmenu on screen */ + centered = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-h")) { /* minimum height of one menu line */ + lineheight = atoi(argv[++i]); + lineheight = MAX(lineheight, min_lineheight); + } + 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-5.1/dmenu.o b/dmenu-5.1/dmenu.o new file mode 100644 index 0000000000000000000000000000000000000000..e97777f3515894bb5fc0783b227f5fdd2a11f601 GIT binary patch literal 37784 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 J*Y{Va{~sa~at8na literal 0 HcmV?d00001 diff --git a/dmenu-5.1/dmenu_path b/dmenu-5.1/dmenu_path new file mode 100755 index 0000000..3a7cda7 --- /dev/null +++ b/dmenu-5.1/dmenu_path @@ -0,0 +1,13 @@ +#!/bin/sh + +cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" +cache="$cachedir/dmenu_run" + +[ ! -e "$cachedir" ] && mkdir -p "$cachedir" + +IFS=: +if stest -dqr -n "$cache" $PATH; then + stest -flx $PATH | sort -u | tee "$cache" +else + cat "$cache" +fi diff --git a/dmenu-5.1/dmenu_run b/dmenu-5.1/dmenu_run new file mode 100755 index 0000000..834ede5 --- /dev/null +++ b/dmenu-5.1/dmenu_run @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & diff --git a/dmenu-5.1/drw.c b/dmenu-5.1/drw.c new file mode 100644 index 0000000..4cdbcbe --- /dev/null +++ b/dmenu-5.1/drw.c @@ -0,0 +1,436 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + /* Do not allow using color fonts. This is a workaround for a BadLength + * error from Xft with color glyphs. Modelled on the Xterm workaround. See + * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + * https://lists.suckless.org/dev/1701/30932.html + * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 + * and lots more all over the internet. + */ + FcBool iscol; + if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { + XftFontClose(drw->dpy, xfont); + return NULL; + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + char buf[1024]; + int ty; + unsigned int ew; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + size_t i, len; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); + /* shorten text if necessary */ + for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + + if (len) { + memcpy(buf, utf8str, len); + buf[len] = '\0'; + if (len < utf8strlen) + for (i = len; i && i > len - 3; buf[--i] = '.') + ; /* NOP */ + + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, len); + } + x += ew; + w -= ew; + } + } + + if (!*text) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/dmenu-5.1/drw.h b/dmenu-5.1/drw.h new file mode 100644 index 0000000..4c67419 --- /dev/null +++ b/dmenu-5.1/drw.h @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/dmenu-5.1/drw.o b/dmenu-5.1/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..3ccf7ec937fe75581a670dbc8e7719ade461f3f3 GIT binary patch literal 10504 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 literal 0 HcmV?d00001 diff --git a/dmenu-5.1/stest b/dmenu-5.1/stest new file mode 100755 index 0000000000000000000000000000000000000000..c1ac5fb1700e867cec0388b522154032ac69deee GIT binary patch literal 21544 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 WT0T#=B_dKW#f1UFff9uhR{RI(k`a^u literal 0 HcmV?d00001 diff --git a/dmenu-5.1/stest.1 b/dmenu-5.1/stest.1 new file mode 100644 index 0000000..2667d8a --- /dev/null +++ b/dmenu-5.1/stest.1 @@ -0,0 +1,90 @@ +.TH STEST 1 dmenu\-VERSION +.SH NAME +stest \- filter a list of files by properties +.SH SYNOPSIS +.B stest +.RB [ -abcdefghlpqrsuwx ] +.RB [ -n +.IR file ] +.RB [ -o +.IR file ] +.RI [ file ...] +.SH DESCRIPTION +.B stest +takes a list of files and filters by the files' properties, analogous to +.IR test (1). +Files which pass all tests are printed to stdout. If no files are given, stest +reads files from stdin. +.SH OPTIONS +.TP +.B \-a +Test hidden files. +.TP +.B \-b +Test that files are block specials. +.TP +.B \-c +Test that files are character specials. +.TP +.B \-d +Test that files are directories. +.TP +.B \-e +Test that files exist. +.TP +.B \-f +Test that files are regular files. +.TP +.B \-g +Test that files have their set-group-ID flag set. +.TP +.B \-h +Test that files are symbolic links. +.TP +.B \-l +Test the contents of a directory given as an argument. +.TP +.BI \-n " file" +Test that files are newer than +.IR file . +.TP +.BI \-o " file" +Test that files are older than +.IR file . +.TP +.B \-p +Test that files are named pipes. +.TP +.B \-q +No files are printed, only the exit status is returned. +.TP +.B \-r +Test that files are readable. +.TP +.B \-s +Test that files are not empty. +.TP +.B \-u +Test that files have their set-user-ID flag set. +.TP +.B \-v +Invert the sense of tests, only failing files pass. +.TP +.B \-w +Test that files are writable. +.TP +.B \-x +Test that files are executable. +.SH EXIT STATUS +.TP +.B 0 +At least one file passed all tests. +.TP +.B 1 +No files passed all tests. +.TP +.B 2 +An error occurred. +.SH SEE ALSO +.IR dmenu (1), +.IR test (1) diff --git a/dmenu-5.1/stest.c b/dmenu-5.1/stest.c new file mode 100644 index 0000000..e27d3a5 --- /dev/null +++ b/dmenu-5.1/stest.c @@ -0,0 +1,109 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include +#include + +#include "arg.h" +char *argv0; + +#define FLAG(x) (flag[(x)-'a']) + +static void test(const char *, const char *); +static void usage(void); + +static int match = 0; +static int flag[26]; +static struct stat old, new; + +static void +test(const char *path, const char *name) +{ + struct stat st, ln; + + if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ + && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ + && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ + && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ + && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ + && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ + && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ + && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ + && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ + && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ + && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ + && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ + && (!FLAG('s') || st.st_size > 0) /* not empty */ + && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ + && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ + && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */ + if (FLAG('q')) + exit(0); + match = 1; + puts(name); + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] " + "[-n file] [-o file] [file...]\n", argv0); + exit(2); /* like test(1) return > 1 on error */ +} + +int +main(int argc, char *argv[]) +{ + struct dirent *d; + char path[PATH_MAX], *line = NULL, *file; + size_t linesiz = 0; + ssize_t n; + DIR *dir; + int r; + + ARGBEGIN { + case 'n': /* newer than file */ + case 'o': /* older than file */ + file = EARGF(usage()); + if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) + perror(file); + break; + default: + /* miscellaneous operators */ + if (strchr("abcdefghlpqrsuvwx", ARGC())) + FLAG(ARGC()) = 1; + else + usage(); /* unknown flag */ + } ARGEND; + + if (!argc) { + /* read list from stdin */ + while ((n = getline(&line, &linesiz, stdin)) > 0) { + if (line[n - 1] == '\n') + line[n - 1] = '\0'; + test(line, line); + } + free(line); + } else { + for (; argc; argc--, argv++) { + if (FLAG('l') && (dir = opendir(*argv))) { + /* test directory contents */ + while ((d = readdir(dir))) { + r = snprintf(path, sizeof path, "%s/%s", + *argv, d->d_name); + if (r >= 0 && (size_t)r < sizeof path) + test(path, d->d_name); + } + closedir(dir); + } else { + test(*argv, *argv); + } + } + } + return match ? 0 : 1; +} diff --git a/dmenu-5.1/stest.o b/dmenu-5.1/stest.o new file mode 100644 index 0000000000000000000000000000000000000000..2f98e41c651acafddcb5d4a3a7bbed0b21eddac4 GIT binary patch literal 5232 zcmbuCeQaA-6~M3UH1V3vFKtQ*>;rFgHcu>%xO^-f>J%sL_Pn_?qGUs~S>o6)AJnmp z{oG_@Q!~Zxa6NYu6xNA9+KGRP5K;w*O?(tj?Y4@s3HJ9iDl`GXOf3_u#+YW#x$hkN z=3(;>N4mcE{O&pD-h1x7?>!&c6T8RlatRifxLdR|rb1-+6B9;dH>yVU z^H_yyM&)DJ_$&VW#?7rsqx{}}qx^5<@xPBi_@q(k8Vo|GQ7#680Kl(m1-#O$RI_?Q zwe^<)ugTb=(7pPY(5=6bYV}nj0so-5BE)VnBShg3?K=F>@j|!caV(gO3vTotT^@JKS%dbdoN4hMD0lC5BkTgDRpAT!3%^$yj$L7G)d5FJ33i{gUpG_pqx%q`u_8Yl;;qdax6u`89ZZTzIE5j`Wx~@-c7XRfT2J@d8%!}}Y zv8p_J;RnK#;jc_qGmZU5^&u}D-ykG;!|@#e5URleE=K}h{OL0)-A38o%8JJ*)wJUK zFz3~gA+2;78pkGF#W%F#+sHl`Dr{L@af?E)v3B0ucy0Z4phlw>Ex&1$J)zp(${m5V zE3WXdNdZ3JeX)2^E1m+A=&m=l()XaDoqM->&$lDh!>88XcP-C^S08hUg0{Dk3V8RH z-YIOB>5DdAs&_YDi>_Vpu6~d27!cYORVfAHQfP2TPg?bLVb z!vZg(9|~>-aN!oN8Zt~Hip!yIv$1eJ@@3&#>2qCwZIAb9FouD|4b*oV)?r^?5UOEf zQM{4Bj=Cehfi=&4zTdk&ce;H8fWkg6tQ@)zlI=dU9rN`)<&OFWj(f&@{%Y5lFZkr9 zuy6Qicf>cc)^oq_lHW7v_Kg4v`+{IH<{JQ)F^u)U1lQFi{?TY><7uvXW3NYMX(PJ^ zRLE5m$~A)q`vxKpwruea<_+!HPUZvHC!n|>{tSg$AcvcX`$>PA>RX7{9Q;<|7byez$yuB-n3GSX;aMQOjBgC zW;(f;6FD=U#5$46<|59H9G`K?uTpn)tVD+~f)3xW6cDKTrG@Hg57m z#Q6h+?K8yRAbXr=KNOzldyY7HvcHCEj2c-X$YG)o^ci8`k^glT``_~;@eqZ3)a6ur>_#f}U zZzaxoI7@ML2me0t5had#4)1o@*U0`p#UB6o@V?9{e7nN=4Tar!ZVytN2((dGsA&Wwp0f&Bp(ip+ zOJ*9p(izLt;p=rszIjNWPZ#uT4qhuc>tS*?7pCWO@g}LgqtIxVzbqyUBuMK( z6~}PqZ$B`MISfS=lOBxt?o@@x=NkX3a9g!o;RmY<+t{Xn^+0iM7yCQs#q;N|MT+V4 zKSSf6VMD5&{`ei+E`KNSp$}r%l{|lVkJ+EkAFkawHo8pEh7Qm^;x`%=r$7FGa-Kav eVgWlqerZ?DUuUlIo!HL*63zd3)6;G^{r>~UK44z} literal 0 HcmV?d00001 diff --git a/dmenu-5.1/util.c b/dmenu-5.1/util.c new file mode 100644 index 0000000..fe044fc --- /dev/null +++ b/dmenu-5.1/util.c @@ -0,0 +1,35 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/dmenu-5.1/util.h b/dmenu-5.1/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/dmenu-5.1/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/dmenu-5.1/util.o b/dmenu-5.1/util.o new file mode 100644 index 0000000000000000000000000000000000000000..6ab501f771232ee94bd1d5e3f8348ea759683421 GIT binary patch literal 2216 zcmbtVJ!n%=6h5!5Hr6zWZHG!7B0MF5`e+J5NmZha_9;PXRn#t;CNDOaKVNPnRl$mN z2qnHbf`g!=(4d2>qmy+M>QDy{=7Uon>yE} zX&_C5Q?NUeEI?$>uAks;0s_zvm(9ku`S`~-q|64>C(OnbJz+Ldx|wPu^_diU*Oyx? z9*idSTft~buLPsd^d*!BqtEqKjxY2zj<5AM9N+0Jj_>u)h*@)`=STz{6AMOSEpz20 zPH^_sY%c3tA~0+=ujnDOnbgNVCH0UGX~dy15e|Px2bLzmTscl28#Nmn)Ub~S%oX&O z9(K3mwZZi;#yJ~nbJ*-#?~~Lz?G0xyIf+kOHNV;99dTmrV8YuBI5K_l zYBU@hH^vR>1$uCVanN>R2)aOP5hwRt6Z3t@kqtQj+WlG3>LG2!-|JgN@i6j-MC>== z=|Jd7*M-2Ah)sytK@qza@Cn8nX#F1e+4hQd?{v2tb*d?sLWKGe2NXT;4Y>n2LPDbg z{SGhwa0fopfj2tvblP6^@qBonvOm60$W3<8LyhGNMh1-3)EH#*7F1c*vTeARtJwJx z%i-KESS83+YAgd443%xL>Ujp1b0M%US2x@N^gU;*k7X!MpVz6n(Fv z|Dy18MzYR^!qeT7{HDUI`dx-lsLyU3QtuZ${Vp1k52H^;dK%JB z2uJere88wK7Fl{8k=YI|NYAzkX@gmH21Yi`(qPP2t6mb}a5HD8i64*ouK$p!3#N2+-BVi3EoYQB2P8{H9v;ARC64MYI1$q zuZ+p{B_=RHKL3bH5#pQ>3@RMCGEd@pg;S+PDNsod?G5DUovC+~QYs?f-Ix3K+(j)( z-T7-Ge};-F=FTr*%HO(^%Y6F2l6kF7JpEsgO$eknO(os=RMUO-NI^u9 aXK13BD7VAV_uni3rRe{`j?!`5`F{Z;iVAT6 literal 0 HcmV?d00001 diff --git a/st-0.8.5/FAQ b/st-0.8.5/FAQ new file mode 100644 index 0000000..969b195 --- /dev/null +++ b/st-0.8.5/FAQ @@ -0,0 +1,250 @@ +## Why does st not handle utmp entries? + +Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task. + + +## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever! + +It means that st doesn’t have any terminfo entry on your system. Chances are +you did not `make install`. If you just want to test it without installing it, +you can manually run `tic -sx st.info`. + + +## Nothing works, and nothing is said about an unknown terminal! + +* Some programs just assume they’re running in xterm i.e. they don’t rely on + terminfo. What you see is the current state of the “xterm compliance”. +* Some programs don’t complain about the lacking st description and default to + another terminal. In that case see the question about terminfo. + + +## How do I scroll back up? + +* Using a terminal multiplexer. + * `st -e tmux` using C-b [ + * `st -e screen` using C-a ESC +* Using the excellent tool of [scroll](https://git.suckless.org/scroll/). +* Using the scrollback [patch](https://st.suckless.org/patches/scrollback/). + + +## I would like to have utmp and/or scroll functionality by default + +You can add the absolute path of both programs in your config.h file. You only +have to modify the value of utmp and scroll variables. + + +## Why doesn't the Del key work in some programs? + +Taken from the terminfo manpage: + + If the terminal has a keypad that transmits codes when the keys + are pressed, this information can be given. Note that it is not + possible to handle terminals where the keypad only works in + local (this applies, for example, to the unshifted HP 2621 keys). + If the keypad can be set to transmit or not transmit, give these + codes as smkx and rmkx. Otherwise the keypad is assumed to + always transmit. + +In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that +applications which want to test against keypad keys send these +sequences. + +But buggy applications (like bash and irssi, for example) don't do this. A fast +solution for them is to use the following command: + + $ printf '\033[?1h\033=' >/dev/tty + +or + $ tput smkx + +In the case of bash, readline is used. Readline has a different note in its +manpage about this issue: + + enable-keypad (Off) + When set to On, readline will try to enable the + application keypad when it is called. Some systems + need this to enable arrow keys. + +Adding this option to your .inputrc will fix the keypad problem for all +applications using readline. + +If you are using zsh, then read the zsh FAQ +: + + It should be noted that the O / [ confusion can occur with other keys + such as Home and End. Some systems let you query the key sequences + sent by these keys from the system's terminal database, terminfo. + Unfortunately, the key sequences given there typically apply to the + mode that is not the one zsh uses by default (it's the "application" + mode rather than the "raw" mode). Explaining the use of terminfo is + outside of the scope of this FAQ, but if you wish to use the key + sequences given there you can tell the line editor to turn on + "application" mode when it starts and turn it off when it stops: + + function zle-line-init () { echoti smkx } + function zle-line-finish () { echoti rmkx } + zle -N zle-line-init + zle -N zle-line-finish + +Putting these lines into your .zshrc will fix the problems. + + +## How can I use meta in 8bit mode? + +St supports meta in 8bit mode, but the default terminfo entry doesn't +use this capability. If you want it, you have to use the 'st-meta' value +in TERM. + + +## I cannot compile st in OpenBSD + +OpenBSD lacks librt, despite it being mandatory in POSIX +. +If you want to compile st for OpenBSD you have to remove -lrt from config.mk, and +st will compile without any loss of functionality, because all the functions are +included in libc on this platform. + + +## The Backspace Case + +St is emulating the Linux way of handling backspace being delete and delete being +backspace. + +This is an issue that was discussed in suckless mailing list +. Here is why some old grumpy +terminal users wants its backspace to be how he feels it: + + Well, I am going to comment why I want to change the behaviour + of this key. When ASCII was defined in 1968, communication + with computers was done using punched cards, or hardcopy + terminals (basically a typewriter machine connected with the + computer using a serial port). ASCII defines DELETE as 7F, + because, in punched-card terms, it means all the holes of the + card punched; it is thus a kind of 'physical delete'. In the + same way, the BACKSPACE key was a non-destructive backspace, + as on a typewriter. So, if you wanted to delete a character, + you had to BACKSPACE and then DELETE. Another use of BACKSPACE + was to type accented characters, for example 'a BACKSPACE `'. + The VT100 had no BACKSPACE key; it was generated using the + CONTROL key as another control character (CONTROL key sets to + 0 b7 b6 b5, so it converts H (code 0x48) into BACKSPACE (code + 0x08)), but it had a DELETE key in a similar position where + the BACKSPACE key is located today on common PC keyboards. + All the terminal emulators emulated the difference between + these keys correctly: the backspace key generated a BACKSPACE + (^H) and delete key generated a DELETE (^?). + + But a problem arose when Linus Torvalds wrote Linux. Unlike + earlier terminals, the Linux virtual terminal (the terminal + emulator integrated in the kernel) returned a DELETE when + backspace was pressed, due to the VT100 having a DELETE key in + the same position. This created a lot of problems (see [1] + and [2]). Since Linux has become the king, a lot of terminal + emulators today generate a DELETE when the backspace key is + pressed in order to avoid problems with Linux. The result is + that the only way of generating a BACKSPACE on these systems + is by using CONTROL + H. (I also think that emacs had an + important point here because the CONTROL + H prefix is used + in emacs in some commands (help commands).) + + From point of view of the kernel, you can change the key + for deleting a previous character with stty erase. When you + connect a real terminal into a machine you describe the type + of terminal, so getty configures the correct value of stty + erase for this terminal. In the case of terminal emulators, + however, you don't have any getty that can set the correct + value of stty erase, so you always get the default value. + For this reason, it is necessary to add 'stty erase ^H' to your + profile if you have changed the value of the backspace key. + Of course, another solution is for st itself to modify the + value of stty erase. I usually have the inverse problem: + when I connect to non-Unix machines, I have to press CONTROL + + h to get a BACKSPACE. The inverse problem occurs when a user + connects to my Unix machines from a different system with a + correct backspace key. + + [1] http://www.ibb.net/~anne/keyboard.html + [2] http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-5.html + + +## But I really want the old grumpy behaviour of my terminal + +Apply [1]. + +[1] https://st.suckless.org/patches/delkey + + +## Why do images not work in st using the w3m image hack? + +w3mimg uses a hack that draws an image on top of the terminal emulator Drawable +window. The hack relies on the terminal to use a single buffer to draw its +contents directly. + +st uses double-buffered drawing so the image is quickly replaced and may show a +short flicker effect. + +Below is a patch example to change st double-buffering to a single Drawable +buffer. + +diff --git a/x.c b/x.c +--- a/x.c ++++ b/x.c +@@ -732,10 +732,6 @@ xresize(int col, int row) + win.tw = col * win.cw; + win.th = row * win.ch; + +- XFreePixmap(xw.dpy, xw.buf); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); +- XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +@@ -1148,8 +1144,7 @@ xinit(int cols, int rows) + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.buf = xw.win; + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + +@@ -1632,8 +1627,6 @@ xdrawline(Line line, int x1, int y1, int x2) + void + xfinishdraw(void) + { +- XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, +- win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); + + +## BadLength X error in Xft when trying to render emoji + +Xft makes st crash when rendering color emojis with the following error: + +"X Error of failed request: BadLength (poly request too large or internal Xlib length error)" + Major opcode of failed request: 139 (RENDER) + Minor opcode of failed request: 20 (RenderAddGlyphs) + Serial number of failed request: 1595 + Current serial number in output stream: 1818" + +This is a known bug in Xft (not st) which happens on some platforms and +combination of particular fonts and fontconfig settings. + +See also: +https://gitlab.freedesktop.org/xorg/lib/libxft/issues/6 +https://bugs.freedesktop.org/show_bug.cgi?id=107534 +https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + +The solution is to remove color emoji fonts or disable this in the fontconfig +XML configuration. As an ugly workaround (which may work only on newer +fontconfig versions (FC_COLOR)), the following code can be used to mask color +fonts: + + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + +Please don't bother reporting this bug to st, but notify the upstream Xft +developers about fixing this bug. diff --git a/st-0.8.5/LEGACY b/st-0.8.5/LEGACY new file mode 100644 index 0000000..bf28b1e --- /dev/null +++ b/st-0.8.5/LEGACY @@ -0,0 +1,17 @@ +A STATEMENT ON LEGACY SUPPORT + +In the terminal world there is much cruft that comes from old and unsup‐ +ported terminals that inherit incompatible modes and escape sequences +which noone is able to know, except when he/she comes from that time and +developed a graphical vt100 emulator at that time. + +One goal of st is to only support what is really needed. When you en‐ +counter a sequence which you really need, implement it. But while you +are at it, do not add the other cruft you might encounter while sneek‐ +ing at other terminal emulators. History has bloated them and there is +no real evidence that most of the sequences are used today. + + +Christoph Lohmann <20h@r-36.net> +2012-09-13T07:00:36.081271045+02:00 + diff --git a/st-0.8.5/LICENSE b/st-0.8.5/LICENSE new file mode 100644 index 0000000..d80eb47 --- /dev/null +++ b/st-0.8.5/LICENSE @@ -0,0 +1,34 @@ +MIT/X Consortium License + +© 2014-2020 Hiltjo Posthuma +© 2018 Devin J. Pohly +© 2014-2017 Quentin Rameau +© 2009-2012 Aurélien APTEL +© 2008-2017 Anselm R Garbe +© 2012-2017 Roberto E. Vargas Caballero +© 2012-2016 Christoph Lohmann <20h at r-36 dot net> +© 2013 Eon S. Jeon +© 2013 Alexander Sedov +© 2013 Mark Edgar +© 2013-2014 Eric Pruitt +© 2013 Michael Forney +© 2013-2014 Markus Teich +© 2014-2015 Laslo Hunhold + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/st-0.8.5/Makefile b/st-0.8.5/Makefile new file mode 100644 index 0000000..38240da --- /dev/null +++ b/st-0.8.5/Makefile @@ -0,0 +1,58 @@ +# st - simple terminal +# See LICENSE file for copyright and license details. +.POSIX: + +include config.mk + +SRC = st.c x.c hb.c +OBJ = $(SRC:.c=.o) + +all: options st + +options: + @echo st build options: + @echo "CFLAGS = $(STCFLAGS)" + @echo "LDFLAGS = $(STLDFLAGS)" + @echo "CC = $(CC)" + +config.h: + cp config.def.h config.h + +.c.o: + $(CC) $(STCFLAGS) -c $< + +st.o: config.h st.h win.h +x.o: arg.h config.h st.h win.h hb.h +hb.o: st.h + +$(OBJ): config.h config.mk + +st: $(OBJ) + $(CC) -o $@ $(OBJ) $(STLDFLAGS) + +clean: + rm -f st $(OBJ) st-$(VERSION).tar.gz + +dist: clean + mkdir -p st-$(VERSION) + cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ + config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ + st-$(VERSION) + tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz + rm -rf st-$(VERSION) + +install: st + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f st $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/st + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 + tic -sx st.info + @echo Please see the README file regarding the terminfo entry of st. + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/st + rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 + +.PHONY: all options clean dist install uninstall diff --git a/st-0.8.5/README b/st-0.8.5/README new file mode 100644 index 0000000..6a846ed --- /dev/null +++ b/st-0.8.5/README @@ -0,0 +1,34 @@ +st - simple terminal +-------------------- +st is a simple terminal emulator for X which sucks less. + + +Requirements +------------ +In order to build st you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (st is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install st (if +necessary as root): + + make clean install + + +Running st +---------- +If you did not install st with make clean install, you must compile +the st terminfo entry with the following command: + + tic -sx st.info + +See the man page for additional details. + +Credits +------- +Based on Aurélien APTEL bt source code. + diff --git a/st-0.8.5/TODO b/st-0.8.5/TODO new file mode 100644 index 0000000..5f74cd5 --- /dev/null +++ b/st-0.8.5/TODO @@ -0,0 +1,28 @@ +vt emulation +------------ + +* double-height support + +code & interface +---------------- + +* add a simple way to do multiplexing + +drawing +------- +* add diacritics support to xdraws() + * switch to a suckless font drawing library +* make the font cache simpler +* add better support for brightening of the upper colors + +bugs +---- + +* fix shift up/down (shift selection in emacs) +* remove DEC test sequence when appropriate + +misc +---- + + $ grep -nE 'XXX|TODO' st.c + diff --git a/st-0.8.5/arg.h b/st-0.8.5/arg.h new file mode 100644 index 0000000..a22e019 --- /dev/null +++ b/st-0.8.5/arg.h @@ -0,0 +1,50 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + int i_;\ + for (i_ = 1, brk_ = 0, argv_ = argv;\ + argv[0][i_] && !brk_;\ + i_++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][i_];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/st-0.8.5/config.def.h b/st-0.8.5/config.def.h new file mode 100644 index 0000000..a82fb39 --- /dev/null +++ b/st-0.8.5/config.def.h @@ -0,0 +1,517 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "JetBrains Mono NL:style=Regular:pixelsize=15:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "black", /* default background colour */ + "gray90", /* default foreground colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 15; +unsigned int defaultbg = 258; +unsigned int defaultcs = 15; +static unsigned int defaultrcs = 15; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 4; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Xresources preferences to load at startup + */ +ResourcePref resources[] = { + { "font", STRING, &font }, + { "color0", STRING, &colorname[0] }, + { "color1", STRING, &colorname[1] }, + { "color2", STRING, &colorname[2] }, + { "color3", STRING, &colorname[3] }, + { "color4", STRING, &colorname[4] }, + { "color5", STRING, &colorname[5] }, + { "color6", STRING, &colorname[6] }, + { "color7", STRING, &colorname[7] }, + { "color8", STRING, &colorname[8] }, + { "color9", STRING, &colorname[9] }, + { "color10", STRING, &colorname[10] }, + { "color11", STRING, &colorname[11] }, + { "color12", STRING, &colorname[12] }, + { "color13", STRING, &colorname[13] }, + { "color14", STRING, &colorname[14] }, + //{ "color15", STRING, &colorname[15] }, + { "background", STRING, &colorname[256] }, + { "foreground", STRING, &colorname[257] }, + { "cursorColor", STRING, &colorname[15] }, + { "termname", STRING, &termname }, + { "shell", STRING, &shell }, + { "minlatency", INTEGER, &minlatency }, + { "maxlatency", INTEGER, &maxlatency }, + { "blinktimeout", INTEGER, &blinktimeout }, + { "bellvolume", INTEGER, &bellvolume }, + { "tabspaces", INTEGER, &tabspaces }, + { "borderpx", INTEGER, &borderpx }, + { "cwscale", FLOAT, &cwscale }, + { "chscale", FLOAT, &chscale }, +}; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/st-0.8.5/config.def.h.orig b/st-0.8.5/config.def.h.orig new file mode 100644 index 0000000..3e829c1 --- /dev/null +++ b/st-0.8.5/config.def.h.orig @@ -0,0 +1,481 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "JetBrains Mono NL:style=Regular:pixelsize=15:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "black", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 15; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/st-0.8.5/config.def.h.rej b/st-0.8.5/config.def.h.rej new file mode 100644 index 0000000..6e45597 --- /dev/null +++ b/st-0.8.5/config.def.h.rej @@ -0,0 +1,45 @@ +--- config.def.h ++++ config.def.h +@@ -168,42 +168,6 @@ static unsigned int defaultattr = 11; + */ + static uint forcemousemod = ShiftMask; + +-/* +- * Xresources preferences to load at startup +- */ +-ResourcePref resources[] = { +- { "font", STRING, &font }, +- { "color0", STRING, &colorname[0] }, +- { "color1", STRING, &colorname[1] }, +- { "color2", STRING, &colorname[2] }, +- { "color3", STRING, &colorname[3] }, +- { "color4", STRING, &colorname[4] }, +- { "color5", STRING, &colorname[5] }, +- { "color6", STRING, &colorname[6] }, +- { "color7", STRING, &colorname[7] }, +- { "color8", STRING, &colorname[8] }, +- { "color9", STRING, &colorname[9] }, +- { "color10", STRING, &colorname[10] }, +- { "color11", STRING, &colorname[11] }, +- { "color12", STRING, &colorname[12] }, +- { "color13", STRING, &colorname[13] }, +- { "color14", STRING, &colorname[14] }, +- { "color15", STRING, &colorname[15] }, +- { "background", STRING, &colorname[256] }, +- { "foreground", STRING, &colorname[257] }, +- { "cursorColor", STRING, &colorname[258] }, +- { "termname", STRING, &termname }, +- { "shell", STRING, &shell }, +- { "minlatency", INTEGER, &minlatency }, +- { "maxlatency", INTEGER, &maxlatency }, +- { "blinktimeout", INTEGER, &blinktimeout }, +- { "bellvolume", INTEGER, &bellvolume }, +- { "tabspaces", INTEGER, &tabspaces }, +- { "borderpx", INTEGER, &borderpx }, +- { "cwscale", FLOAT, &cwscale }, +- { "chscale", FLOAT, &chscale }, +-}; +- + /* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. diff --git a/st-0.8.5/config.h b/st-0.8.5/config.h new file mode 100644 index 0000000..a82fb39 --- /dev/null +++ b/st-0.8.5/config.h @@ -0,0 +1,517 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "JetBrains Mono NL:style=Regular:pixelsize=15:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "black", /* default background colour */ + "gray90", /* default foreground colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 15; +unsigned int defaultbg = 258; +unsigned int defaultcs = 15; +static unsigned int defaultrcs = 15; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 4; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Xresources preferences to load at startup + */ +ResourcePref resources[] = { + { "font", STRING, &font }, + { "color0", STRING, &colorname[0] }, + { "color1", STRING, &colorname[1] }, + { "color2", STRING, &colorname[2] }, + { "color3", STRING, &colorname[3] }, + { "color4", STRING, &colorname[4] }, + { "color5", STRING, &colorname[5] }, + { "color6", STRING, &colorname[6] }, + { "color7", STRING, &colorname[7] }, + { "color8", STRING, &colorname[8] }, + { "color9", STRING, &colorname[9] }, + { "color10", STRING, &colorname[10] }, + { "color11", STRING, &colorname[11] }, + { "color12", STRING, &colorname[12] }, + { "color13", STRING, &colorname[13] }, + { "color14", STRING, &colorname[14] }, + //{ "color15", STRING, &colorname[15] }, + { "background", STRING, &colorname[256] }, + { "foreground", STRING, &colorname[257] }, + { "cursorColor", STRING, &colorname[15] }, + { "termname", STRING, &termname }, + { "shell", STRING, &shell }, + { "minlatency", INTEGER, &minlatency }, + { "maxlatency", INTEGER, &maxlatency }, + { "blinktimeout", INTEGER, &blinktimeout }, + { "bellvolume", INTEGER, &bellvolume }, + { "tabspaces", INTEGER, &tabspaces }, + { "borderpx", INTEGER, &borderpx }, + { "cwscale", FLOAT, &cwscale }, + { "chscale", FLOAT, &chscale }, +}; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/st-0.8.5/config.mk b/st-0.8.5/config.mk new file mode 100644 index 0000000..ef6de39 --- /dev/null +++ b/st-0.8.5/config.mk @@ -0,0 +1,37 @@ +# st version +VERSION = 0.8.5 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# includes and libs +INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` \ + `$(PKG_CONFIG) --cflags harfbuzz` +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` \ + `$(PKG_CONFIG) --libs harfbuzz` + +# flags +STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +STLDFLAGS = $(LIBS) $(LDFLAGS) + +# OpenBSD: +#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE +#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ +# `$(PKG_CONFIG) --libs fontconfig` \ +# `$(PKG_CONFIG) --libs freetype2` + +# compiler and linker +# CC = c99 diff --git a/st-0.8.5/config.mk.orig b/st-0.8.5/config.mk.orig new file mode 100644 index 0000000..ef6de39 --- /dev/null +++ b/st-0.8.5/config.mk.orig @@ -0,0 +1,37 @@ +# st version +VERSION = 0.8.5 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# includes and libs +INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` \ + `$(PKG_CONFIG) --cflags harfbuzz` +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` \ + `$(PKG_CONFIG) --libs harfbuzz` + +# flags +STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +STLDFLAGS = $(LIBS) $(LDFLAGS) + +# OpenBSD: +#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE +#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ +# `$(PKG_CONFIG) --libs fontconfig` \ +# `$(PKG_CONFIG) --libs freetype2` + +# compiler and linker +# CC = c99 diff --git a/st-0.8.5/hb.c b/st-0.8.5/hb.c new file mode 100644 index 0000000..f881f44 --- /dev/null +++ b/st-0.8.5/hb.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include + +#include "st.h" + +#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END } + +void hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length); +hb_font_t *hbfindfont(XftFont *match); + +typedef struct { + XftFont *match; + hb_font_t *font; +} HbFontMatch; + +static int hbfontslen = 0; +static HbFontMatch *hbfontcache = NULL; + +/* + * Poplulate the array with a list of font features, wrapped in FEATURE macro, + * e. g. + * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') + */ +hb_feature_t features[] = { }; + +void +hbunloadfonts() +{ + for (int i = 0; i < hbfontslen; i++) { + hb_font_destroy(hbfontcache[i].font); + XftUnlockFace(hbfontcache[i].match); + } + + if (hbfontcache != NULL) { + free(hbfontcache); + hbfontcache = NULL; + } + hbfontslen = 0; +} + +hb_font_t * +hbfindfont(XftFont *match) +{ + for (int i = 0; i < hbfontslen; i++) { + if (hbfontcache[i].match == match) + return hbfontcache[i].font; + } + + /* Font not found in cache, caching it now. */ + hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1)); + FT_Face face = XftLockFace(match); + hb_font_t *font = hb_ft_font_create(face, NULL); + if (font == NULL) + die("Failed to load Harfbuzz font."); + + hbfontcache[hbfontslen].match = match; + hbfontcache[hbfontslen].font = font; + hbfontslen += 1; + + return font; +} + +void +hbtransform(XftGlyphFontSpec *specs, const Glyph *glyphs, size_t len, int x, int y) +{ + int start = 0, length = 1, gstart = 0; + hb_codepoint_t *codepoints = calloc((unsigned int)len, sizeof(hb_codepoint_t)); + + for (int idx = 1, specidx = 1; idx < len; idx++) { + if (glyphs[idx].mode & ATTR_WDUMMY) { + length += 1; + continue; + } + + if (specs[specidx].font != specs[start].font || ATTRCMP(glyphs[gstart], glyphs[idx]) || selected(x + idx, y) != selected(x + gstart, y)) { + hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length); + + /* Reset the sequence. */ + length = 1; + start = specidx; + gstart = idx; + } else { + length += 1; + } + + specidx++; + } + + /* EOL. */ + hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length); + + /* Apply the transformation to glyph specs. */ + for (int i = 0, specidx = 0; i < len; i++) { + if (glyphs[i].mode & ATTR_WDUMMY) + continue; + + if (codepoints[i] != specs[specidx].glyph) + ((Glyph *)glyphs)[i].mode |= ATTR_LIGA; + + specs[specidx++].glyph = codepoints[i]; + } + + free(codepoints); +} + +void +hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length) +{ + hb_font_t *font = hbfindfont(xfont); + if (font == NULL) + return; + + Rune rune; + ushort mode = USHRT_MAX; + hb_buffer_t *buffer = hb_buffer_create(); + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); + + /* Fill buffer with codepoints. */ + for (int i = start; i < (start+length); i++) { + rune = string[i].u; + mode = string[i].mode; + if (mode & ATTR_WDUMMY) + rune = 0x0020; + hb_buffer_add_codepoints(buffer, &rune, 1, 0, 1); + } + + /* Shape the segment. */ + hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); + + /* Get new glyph info. */ + hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, NULL); + + /* Write new codepoints. */ + for (int i = 0; i < length; i++) { + hb_codepoint_t gid = info[i].codepoint; + codepoints[start+i] = gid; + } + + /* Cleanup. */ + hb_buffer_destroy(buffer); +} diff --git a/st-0.8.5/hb.h b/st-0.8.5/hb.h new file mode 100644 index 0000000..07888df --- /dev/null +++ b/st-0.8.5/hb.h @@ -0,0 +1,6 @@ +#include +#include +#include + +void hbunloadfonts(); +void hbtransform(XftGlyphFontSpec *, const Glyph *, size_t, int, int); diff --git a/st-0.8.5/hb.o b/st-0.8.5/hb.o new file mode 100644 index 0000000000000000000000000000000000000000..b9507a545077a6e6d0b002cdc208469ebe8f8d28 GIT binary patch literal 4632 zcmbtYX>1!+5Pq9%+ocI>11(sT!=MEj(VC`I5JQ2i>xO6H@&ghM(PEtSI*#ht>3S`i zQZBhEtQK?pLjnoJKM)8INR+Ehnx^5DKmrvZ0p%*7P_>yOd5_vfsuOR0uu1@Yk zKCVpAkx_fKyA&a6?4e@^WZKFDBGf4=^*xf~S)CtwWz;PyQ%7in<=8n(HubuGvwoA_ ztM{k zE6BQ$L!pN!5j7?-9ts<$W8cad=`VeE6!7V_3*?k@G?Z!ut415Prs+Fzgw9FHI8r-27XpLK(~G?!*Di;N>%48FzCqup->zrJPb}}$%dFtwr2a#l z-_p*0Z)!7|mcj{j_On@I0c?iS^%*I&<8K(a{x%MOGNrXcL-gHTi=xI%Q(by}QkuFr z?XJ<*IY-@$)=u3Xoa8SVpHtG>Q}7e@Vs$oyFpG(j77lHH5ooCM6CL|n4!xFYlc)E} zwHGr+UW=y3zLVEWzo(5)akr-3)ml2S`t;i4)aZc>Yy*E@xHKt0J2Op4?S41arX-R| zXvChGnE|Cek!%l*^fN`%8f#*%t$9ZZ2D}{BM1vyRrTw#T0^rcdWe|Yl`Kbpw^N*Su zDJ_aS9Y(y6k2nK_k&JUp44S$rSadiW-LP9^FsyI1Kft!d)M@p7lCwp@U4#ptNxWyD z7kaI2dP1&ExmQqQA5v$eBUf%w>`(6dP$@WVQ*iWw-&>yJ_W~|McW$6$=A(8cTB_vo zQJS*~eYLGyqlHS@juUe2+O=0iW7l5adr`DwReV)^CB~rOL5aeH=M~JPg-D}_;S!@r z0ey)F6KU%WMA{OTmbEWZ9tCF&@UytBhOMrKceLngq~4lRqb(&h0)Vb6F)*dL-z)$1 zdu*|A)Y}LJ`w;i-#e$M*_>q>R8hN-isYV-eQjI;dKv!4pSg5O=^+hRls5K-lRyzUG z)fl*dD}*FfMQ_VM_*~dSI)_4ZJxY+Eaab+1dbx8$fV#SJrej!LM(E-v~JJ3tqOSfZg82 zzt9A?n&8`;;12?hd1E-?AZ&j?`vg4mjqCX)_}(V?7l5BK&$=9J;(xdaeiGC-Up>p3 z;L8EWaoyblgdl9MLfhHI|C%N^DfYz;_`&mEEtzHBHF8GLB=OAJl>pSrcvmnF(6ZRc z=FO^YRffqe1-lo_#-{F^VUmJnn#eFvX70o?b0sjMS4Q|CgYA(RV7E<@AHX2n%9X2y ziZxg@`v=Xkjj8%-g@S2ix!)X6HKBq5%QWnP3YIJ!bNPJMsN~I|$^Z<96o9^FnN{?t z7IQbx5j-0L{#+$^4p9WZxKHk5=o~Kcx&d6|eYFYBzE8l0e#jx}8HGNI$SZi> z@nGhUAFIOuQy-3>)&l>QH@9$AkQ) z&)?5)`EWn~un&*;_@DFPXZrACJ{<2uQICs<3Iyw129Lm>X@Vd2;eMVpUubOn`#%ag z3Z~~JFrBjBIlRVoJAjM#%V+=>_cMuChX?IkAGEgRwMEgh%u+6Hn_FxW&*$tMiT72j zByLquLHCY$2Z%_3gsC|+hDqkO&atYECTXK>G;TnG>1+zlZn zPX_V|JeYqu7?3j<|1^&m->V`%_%7}M19Gks4!jld;+_{~kzcf3V8H%L;lNw=w-og8 zZ2LvTi8c*J(f9j(e1hY@6@nJQG|R#{Qo$2A(>Bt@&5u(^Fm$# literal 0 HcmV?d00001 diff --git a/st-0.8.5/st b/st-0.8.5/st new file mode 100755 index 0000000000000000000000000000000000000000..3137fa6619645b2d6ed249977241934d7d1682cf GIT binary patch literal 119560 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 Qeno!;6UVJ3hSH__4`E2w(f|Me literal 0 HcmV?d00001 diff --git a/st-0.8.5/st.1 b/st-0.8.5/st.1 new file mode 100644 index 0000000..39120b4 --- /dev/null +++ b/st-0.8.5/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-0.8.5/st.c b/st-0.8.5/st.c new file mode 100644 index 0000000..93206dd --- /dev/null +++ b/st-0.8.5/st.c @@ -0,0 +1,2767 @@ +/* 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 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; +} + +static const char base64_digits[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint(**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + + 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() +{ + /* 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(); +} + +int tisaltscr(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (term.scr == 0) + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (term.scr == 0) + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc4_color_response(int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(num, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num); + return; + } + + n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +osc_color_response(int index, int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc color %d\n", index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + + 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: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultfg, 10); + else if (xsetcolorname(defaultfg, p)) + fprintf(stderr, "erresc: invalid foreground color: %s\n", p); + else + redraw(); + return; + case 11: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultbg, 11); + else if (xsetcolorname(defaultbg, p)) + fprintf(stderr, "erresc: invalid background color: %s\n", p); + else + redraw(); + return; + case 12: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultcs, 12); + else if (xsetcolorname(defaultcs, p)) + fprintf(stderr, "erresc: invalid cursor color: %s\n", p); + else + redraw(); + 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, "?")) + osc4_color_response(j); + else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + redraw(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(TLINE(y), x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx], + term.line[term.ocy], term.col); + 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-0.8.5/st.c.orig b/st-0.8.5/st.c.orig new file mode 100644 index 0000000..68ae94a --- /dev/null +++ b/st-0.8.5/st.c.orig @@ -0,0 +1,2766 @@ +/* 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 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; +} + +static const char base64_digits[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint(**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + + 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() +{ + /* 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(); +} + +int tisaltscr(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (term.scr == 0) + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (term.scr == 0) + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc4_color_response(int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(num, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num); + return; + } + + n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +osc_color_response(int index, int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc color %d\n", index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + + 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: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultfg, 10); + else if (xsetcolorname(defaultfg, p)) + fprintf(stderr, "erresc: invalid foreground color: %s\n", p); + else + redraw(); + return; + case 11: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultbg, 11); + else if (xsetcolorname(defaultbg, p)) + fprintf(stderr, "erresc: invalid background color: %s\n", p); + else + redraw(); + return; + case 12: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultcs, 12); + else if (xsetcolorname(defaultcs, p)) + fprintf(stderr, "erresc: invalid cursor color: %s\n", p); + else + redraw(); + 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, "?")) + osc4_color_response(j); + else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + redraw(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(TLINE(y), x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st-0.8.5/st.h b/st-0.8.5/st.h new file mode 100644 index 0000000..012c025 --- /dev/null +++ b/st-0.8.5/st.h @@ -0,0 +1,134 @@ +/* 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 & (~ATTR_WRAP) & (~ATTR_LIGA)) != ((b).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) || \ + (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_LIGA = 1 << 11, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +int tisaltscr(void); +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 xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; +extern unsigned int defaultcs; +extern float alpha; diff --git a/st-0.8.5/st.info b/st-0.8.5/st.info new file mode 100644 index 0000000..8201ad6 --- /dev/null +++ b/st-0.8.5/st.info @@ -0,0 +1,239 @@ +st-mono| simpleterm monocolor, + acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, + am, + bce, + bel=^G, + blink=\E[5m, + bold=\E[1m, + cbt=\E[Z, + cvvis=\E[?25h, + civis=\E[?25l, + clear=\E[H\E[2J, + cnorm=\E[?12l\E[?25h, + colors#2, + cols#80, + cr=^M, + csr=\E[%i%p1%d;%p2%dr, + cub=\E[%p1%dD, + cub1=^H, + cud1=^J, + cud=\E[%p1%dB, + cuf1=\E[C, + cuf=\E[%p1%dC, + cup=\E[%i%p1%d;%p2%dH, + cuu1=\E[A, + cuu=\E[%p1%dA, + dch=\E[%p1%dP, + dch1=\E[P, + dim=\E[2m, + dl=\E[%p1%dM, + dl1=\E[M, + ech=\E[%p1%dX, + ed=\E[J, + el=\E[K, + el1=\E[1K, + enacs=\E)0, + flash=\E[?5h$<80/>\E[?5l, + fsl=^G, + home=\E[H, + hpa=\E[%i%p1%dG, + hs, + ht=^I, + hts=\EH, + ich=\E[%p1%d@, + il1=\E[L, + il=\E[%p1%dL, + ind=^J, + indn=\E[%p1%dS, + invis=\E[8m, + is2=\E[4l\E>\E[?1034l, + it#8, + kel=\E[1;2F, + ked=\E[1;5F, + ka1=\E[1~, + ka3=\E[5~, + kc1=\E[4~, + kc3=\E[6~, + kbs=\177, + kcbt=\E[Z, + kb2=\EOu, + kcub1=\EOD, + kcud1=\EOB, + kcuf1=\EOC, + kcuu1=\EOA, + kDC=\E[3;2~, + kent=\EOM, + kEND=\E[1;2F, + kIC=\E[2;2~, + kNXT=\E[6;2~, + kPRV=\E[5;2~, + kHOM=\E[1;2H, + kLFT=\E[1;2D, + kRIT=\E[1;2C, + kind=\E[1;2B, + kri=\E[1;2A, + kclr=\E[3;5~, + kdl1=\E[3;2~, + kdch1=\E[3~, + kich1=\E[2~, + kend=\E[4~, + kf1=\EOP, + kf2=\EOQ, + kf3=\EOR, + kf4=\EOS, + kf5=\E[15~, + kf6=\E[17~, + kf7=\E[18~, + kf8=\E[19~, + kf9=\E[20~, + kf10=\E[21~, + kf11=\E[23~, + kf12=\E[24~, + kf13=\E[1;2P, + kf14=\E[1;2Q, + kf15=\E[1;2R, + kf16=\E[1;2S, + kf17=\E[15;2~, + kf18=\E[17;2~, + kf19=\E[18;2~, + kf20=\E[19;2~, + kf21=\E[20;2~, + kf22=\E[21;2~, + kf23=\E[23;2~, + kf24=\E[24;2~, + kf25=\E[1;5P, + kf26=\E[1;5Q, + kf27=\E[1;5R, + kf28=\E[1;5S, + kf29=\E[15;5~, + kf30=\E[17;5~, + kf31=\E[18;5~, + kf32=\E[19;5~, + kf33=\E[20;5~, + kf34=\E[21;5~, + kf35=\E[23;5~, + kf36=\E[24;5~, + kf37=\E[1;6P, + kf38=\E[1;6Q, + kf39=\E[1;6R, + kf40=\E[1;6S, + kf41=\E[15;6~, + kf42=\E[17;6~, + kf43=\E[18;6~, + kf44=\E[19;6~, + kf45=\E[20;6~, + kf46=\E[21;6~, + kf47=\E[23;6~, + kf48=\E[24;6~, + kf49=\E[1;3P, + kf50=\E[1;3Q, + kf51=\E[1;3R, + kf52=\E[1;3S, + kf53=\E[15;3~, + kf54=\E[17;3~, + kf55=\E[18;3~, + kf56=\E[19;3~, + kf57=\E[20;3~, + kf58=\E[21;3~, + kf59=\E[23;3~, + kf60=\E[24;3~, + kf61=\E[1;4P, + kf62=\E[1;4Q, + kf63=\E[1;4R, + khome=\E[1~, + kil1=\E[2;5~, + krmir=\E[2;2~, + knp=\E[6~, + kmous=\E[M, + kpp=\E[5~, + lines#24, + mir, + msgr, + npc, + op=\E[39;49m, + pairs#64, + mc0=\E[i, + mc4=\E[4i, + mc5=\E[5i, + rc=\E8, + rev=\E[7m, + ri=\EM, + rin=\E[%p1%dT, + ritm=\E[23m, + rmacs=\E(B, + rmcup=\E[?1049l, + rmir=\E[4l, + rmkx=\E[?1l\E>, + rmso=\E[27m, + rmul=\E[24m, + rs1=\Ec, + rs2=\E[4l\E>\E[?1034l, + sc=\E7, + sitm=\E[3m, + sgr0=\E[0m, + smacs=\E(0, + smcup=\E[?1049h, + smir=\E[4h, + smkx=\E[?1h\E=, + smso=\E[7m, + smul=\E[4m, + tbc=\E[3g, + tsl=\E]0;, + xenl, + vpa=\E[%i%p1%dd, +# XTerm extensions + rmxx=\E[29m, + smxx=\E[9m, +# disabled rep for now: causes some issues with older ncurses versions. +# rep=%p1%c\E[%p2%{1}%-%db, +# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) + Tc, + Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, + +st| simpleterm, + use=st-mono, + colors#8, + setab=\E[4%p1%dm, + setaf=\E[3%p1%dm, + setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, + +st-256color| simpleterm with 256 colors, + use=st, + ccc, + colors#256, + oc=\E]104\007, + pairs#32767, +# Nicked from xterm-256color + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, + +st-meta| simpleterm with meta key, + use=st, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-meta-256color| simpleterm with meta key and 256 colors, + use=st-256color, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-bs| simpleterm with backspace as backspace, + use=st, + kbs=\010, + kdch1=\177, + +st-bs-256color| simpleterm with backspace as backspace and 256colors, + use=st-256color, + kbs=\010, + kdch1=\177, diff --git a/st-0.8.5/st.o b/st-0.8.5/st.o new file mode 100644 index 0000000000000000000000000000000000000000..ac9c52b13f1af5d70c08541ab2b6f405976f5d6d GIT binary patch literal 81696 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 +#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" +#include "hb.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; + int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* Xresources preferences */ +enum resource_type { + STRING = 0, + INTEGER = 1, + FLOAT = 2 +}; + +typedef struct { + char *name; + enum resource_type type; + void *dst; +} ResourcePref; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_alpha = NULL; +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static int oldbutton = 3; /* button event on startup: 3 = release */ + +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, x = evcol(e), y = evrow(e), + button = e->xbutton.button, state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + /* from urxvt */ + if (e->xbutton.type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MOUSE_MOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) + return; + + button = oldbutton + 32; + ox = x; + oy = y; + } else { + if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { + button = 3; + } else { + button -= Button1; + if (button >= 7) + button += 128 - 7; + else if (button >= 3) + button += 64 - 3; + } + if (e->xbutton.type == ButtonPress) { + oldbutton = button; + ox = x; + oy = y; + } else if (e->xbutton.type == ButtonRelease) { + oldbutton = 3; + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + if (button == 64 || button == 65) + return; + } + } + + if (!IS_SET(MODE_MOUSEX10)) { + button += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod4Mask ) ? 8 : 0) + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + button, x+1, y+1, + e->xbutton.type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+button, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + struct timespec now; + int snap; + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (e->xbutton.button == 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) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (e->xbutton.button == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + + /* set alpha value of bg color */ + if (opt_alpha) + alpha = strtof(opt_alpha, NULL); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : "st", + opt_class ? opt_class : "St"}; + 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) +{ + /* Clear Harfbuzz font cache. */ + hbunloadfonts(); + + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; + + xw.scr = XDefaultScreen(xw.dpy); + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { + parent = XRootWindow(xw.dpy, xw.scr); + xw.depth = 32; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.depth = attr.depth; + } + + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); + xw.vis = vis.visual; + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode & ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + /* Harfbuzz transformation for ligatures. */ + hbtransform(specs, glyphs, len, x, y); + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 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, Line line, int len) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + + /* Redraw the line where cursor was previously. + * It will restore the ligatures broken by the cursor. */ + xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +int +resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) +{ + char **sdst = dst; + int *idst = dst; + float *fdst = dst; + + char fullname[256]; + char fullclass[256]; + char *type; + XrmValue ret; + + snprintf(fullname, sizeof(fullname), "%s.%s", + opt_name ? opt_name : "st", name); + snprintf(fullclass, sizeof(fullclass), "%s.%s", + opt_class ? opt_class : "St", name); + fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; + + XrmGetResource(db, fullname, fullclass, &type, &ret); + if (ret.addr == NULL || strncmp("String", type, 64)) + return 1; + + switch (rtype) { + case STRING: + *sdst = ret.addr; + break; + case INTEGER: + *idst = strtoul(ret.addr, NULL, 10); + break; + case FLOAT: + *fdst = strtof(ret.addr, NULL); + break; + } + return 0; +} + +void +config_init(void) +{ + char *resm; + XrmDatabase db; + ResourcePref *p; + + XrmInitialize(); + resm = XResourceManagerString(xw.dpy); + if (!resm) + return; + + db = XrmGetStringDatabase(resm); + for (p = resources; p < resources + LEN(resources); p++) + resource_load(db, p->name, p->type, p->dst); +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'A': + opt_alpha = EARGF(usage()); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + + if(!(xw.dpy = XOpenDisplay(NULL))) + die("Can't open display\n"); + + config_init(); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/st-0.8.5/x.c.orig b/st-0.8.5/x.c.orig new file mode 100644 index 0000000..fc716f7 --- /dev/null +++ b/st-0.8.5/x.c.orig @@ -0,0 +1,2112 @@ +/* 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" +#include "hb.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; + int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_alpha = NULL; +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static int oldbutton = 3; /* button event on startup: 3 = release */ + +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, x = evcol(e), y = evrow(e), + button = e->xbutton.button, state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + /* from urxvt */ + if (e->xbutton.type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MOUSE_MOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) + return; + + button = oldbutton + 32; + ox = x; + oy = y; + } else { + if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { + button = 3; + } else { + button -= Button1; + if (button >= 7) + button += 128 - 7; + else if (button >= 3) + button += 64 - 3; + } + if (e->xbutton.type == ButtonPress) { + oldbutton = button; + ox = x; + oy = y; + } else if (e->xbutton.type == ButtonRelease) { + oldbutton = 3; + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + if (button == 64 || button == 65) + return; + } + } + + if (!IS_SET(MODE_MOUSEX10)) { + button += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod4Mask ) ? 8 : 0) + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + button, x+1, y+1, + e->xbutton.type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+button, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + struct timespec now; + int snap; + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (e->xbutton.button == 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) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (e->xbutton.button == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + + /* set alpha value of bg color */ + if (opt_alpha) + alpha = strtof(opt_alpha, NULL); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Clear Harfbuzz font cache. */ + hbunloadfonts(); + + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { + parent = XRootWindow(xw.dpy, xw.scr); + xw.depth = 32; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.depth = attr.depth; + } + + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); + xw.vis = vis.visual; + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode & ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + /* Harfbuzz transformation for ligatures. */ + hbtransform(specs, glyphs, len, x, y); + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 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, Line line, int len) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + + /* Redraw the line where cursor was previously. + * It will restore the ligatures broken by the cursor. */ + xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'A': + opt_alpha = EARGF(usage()); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/st-0.8.5/x.o b/st-0.8.5/x.o new file mode 100644 index 0000000000000000000000000000000000000000..36380edcb17302290bdc093a50264b0dc135fce6 GIT binary patch literal 81392 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 S=Ns9l__AOKH#!5CP5uQ{ua=tt literal 0 HcmV?d00001