├── hb.h ├── README.md ├── config.mk ├── arg.h ├── win.h ├── Makefile ├── LICENSE ├── st.h ├── hb.c ├── st.1 ├── st.info ├── config.h ├── x.c └── st.c /hb.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef struct { 6 | hb_buffer_t *buffer; 7 | hb_glyph_info_t *glyphs; 8 | hb_glyph_position_t *positions; 9 | unsigned int count; 10 | } HbTransformData; 11 | 12 | void hbunloadfonts(); 13 | void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); 14 | void hbcleanup(HbTransformData *); 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## simple terminal 2 | My very simple fork of st, comes with no guarantees or warranties (to be clear: this means things may not work as expected, or at all) :^) 3 | 4 | ## patches added 5 | * alpha & changealpha (transparency) 6 | * Xresources w/ reload signal (pywal takes priority) 7 | * ligatures 8 | * scrollback ringbuffer, with mouse 9 | * anysize (ensures compatibility with various gaps setups in tiling WMs) 10 | 11 | ## other stuff 12 | * If you aren't using ```~/.Xresources``` or [pywal](https://github.com/dylanaraps/pywal), default color palette is [Nord](https://www.nordtheme.com/). 13 | * Read or change keybinds, default font/size, etc. in **config.h** - I'll update the man page at some point. Bindings are what you'd expect, besides: 14 | - ```alt + c``` & ```alt + v``` for copy-paste 15 | - ```alt + a``` & ```alt + s``` to increase and decrease alpha (transparency) respectively 16 | - ```alt + shift + k``` & ```alt + shift + j``` to increase and decrease font size, respectively 17 | 18 | ## how install pls? 19 | ``` 20 | git clone https://github.com/BreadOnPenguins/st 21 | cd st 22 | sudo make install 23 | ``` 24 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # st version 2 | VERSION = 0.9.2 3 | 4 | # Customize below to fit your system 5 | 6 | # paths 7 | PREFIX = /usr/local 8 | MANPREFIX = $(PREFIX)/share/man 9 | 10 | X11INC = /usr/X11R6/include 11 | X11LIB = /usr/X11R6/lib 12 | 13 | PKG_CONFIG = pkg-config 14 | 15 | # includes and libs 16 | INCS = -I$(X11INC) \ 17 | `$(PKG_CONFIG) --cflags fontconfig` \ 18 | `$(PKG_CONFIG) --cflags freetype2` \ 19 | `$(PKG_CONFIG) --cflags harfbuzz` 20 | LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ 21 | `$(PKG_CONFIG) --libs fontconfig` \ 22 | `$(PKG_CONFIG) --libs freetype2` \ 23 | `$(PKG_CONFIG) --libs harfbuzz` 24 | 25 | # flags 26 | STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 27 | STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) 28 | STLDFLAGS = $(LIBS) $(LDFLAGS) 29 | 30 | # OpenBSD: 31 | #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE 32 | #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ 33 | # `$(PKG_CONFIG) --libs fontconfig` \ 34 | # `$(PKG_CONFIG) --libs freetype2` 35 | #MANPREFIX = ${PREFIX}/man 36 | 37 | # compiler and linker 38 | # CC = c99 39 | -------------------------------------------------------------------------------- /arg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copy me if you can. 3 | * by 20h 4 | */ 5 | 6 | #ifndef ARG_H__ 7 | #define ARG_H__ 8 | 9 | extern char *argv0; 10 | 11 | /* use main(int argc, char *argv[]) */ 12 | #define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ 13 | argv[0] && argv[0][0] == '-'\ 14 | && argv[0][1];\ 15 | argc--, argv++) {\ 16 | char argc_;\ 17 | char **argv_;\ 18 | int brk_;\ 19 | if (argv[0][1] == '-' && argv[0][2] == '\0') {\ 20 | argv++;\ 21 | argc--;\ 22 | break;\ 23 | }\ 24 | int i_;\ 25 | for (i_ = 1, brk_ = 0, argv_ = argv;\ 26 | argv[0][i_] && !brk_;\ 27 | i_++) {\ 28 | if (argv_ != argv)\ 29 | break;\ 30 | argc_ = argv[0][i_];\ 31 | switch (argc_) 32 | 33 | #define ARGEND }\ 34 | } 35 | 36 | #define ARGC() argc_ 37 | 38 | #define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ 39 | ((x), abort(), (char *)0) :\ 40 | (brk_ = 1, (argv[0][i_+1] != '\0')?\ 41 | (&argv[0][i_+1]) :\ 42 | (argc--, argv++, argv[0]))) 43 | 44 | #define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ 45 | (char *)0 :\ 46 | (brk_ = 1, (argv[0][i_+1] != '\0')?\ 47 | (&argv[0][i_+1]) :\ 48 | (argc--, argv++, argv[0]))) 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /win.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | 3 | enum win_mode { 4 | MODE_VISIBLE = 1 << 0, 5 | MODE_FOCUSED = 1 << 1, 6 | MODE_APPKEYPAD = 1 << 2, 7 | MODE_MOUSEBTN = 1 << 3, 8 | MODE_MOUSEMOTION = 1 << 4, 9 | MODE_REVERSE = 1 << 5, 10 | MODE_KBDLOCK = 1 << 6, 11 | MODE_HIDE = 1 << 7, 12 | MODE_APPCURSOR = 1 << 8, 13 | MODE_MOUSESGR = 1 << 9, 14 | MODE_8BIT = 1 << 10, 15 | MODE_BLINK = 1 << 11, 16 | MODE_FBLINK = 1 << 12, 17 | MODE_FOCUS = 1 << 13, 18 | MODE_MOUSEX10 = 1 << 14, 19 | MODE_MOUSEMANY = 1 << 15, 20 | MODE_BRCKTPASTE = 1 << 16, 21 | MODE_NUMLOCK = 1 << 17, 22 | MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ 23 | |MODE_MOUSEMANY, 24 | }; 25 | 26 | void xbell(void); 27 | void xclipcopy(void); 28 | void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); 29 | void xdrawline(Line, int, int, int); 30 | void xfinishdraw(void); 31 | void xloadcols(void); 32 | int xsetcolorname(int, const char *); 33 | int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *); 34 | void xseticontitle(char *); 35 | void xsettitle(char *); 36 | int xsetcursor(int); 37 | void xsetmode(int, unsigned int); 38 | void xsetpointermotion(int); 39 | void xsetsel(char *); 40 | int xstartdraw(void); 41 | void xximspot(int, int); 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # st - simple terminal 2 | # See LICENSE file for copyright and license details. 3 | .POSIX: 4 | 5 | include config.mk 6 | 7 | SRC = st.c x.c hb.c 8 | OBJ = $(SRC:.c=.o) 9 | 10 | all: st 11 | 12 | config.h: 13 | cp config.def.h config.h 14 | 15 | .c.o: 16 | $(CC) $(STCFLAGS) -c $< 17 | 18 | st.o: config.h st.h win.h 19 | x.o: arg.h config.h st.h win.h hb.h 20 | hb.o: st.h 21 | 22 | $(OBJ): config.h config.mk 23 | 24 | st: $(OBJ) 25 | $(CC) -o $@ $(OBJ) $(STLDFLAGS) 26 | 27 | clean: 28 | rm -f st $(OBJ) st-$(VERSION).tar.gz 29 | 30 | dist: clean 31 | mkdir -p st-$(VERSION) 32 | cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ 33 | config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ 34 | st-$(VERSION) 35 | tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz 36 | rm -rf st-$(VERSION) 37 | 38 | install: st 39 | mkdir -p $(DESTDIR)$(PREFIX)/bin 40 | cp -f st $(DESTDIR)$(PREFIX)/bin 41 | chmod 755 $(DESTDIR)$(PREFIX)/bin/st 42 | mkdir -p $(DESTDIR)$(MANPREFIX)/man1 43 | sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 44 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 45 | tic -sx st.info 46 | @echo Please see the README file regarding the terminfo entry of st. 47 | 48 | uninstall: 49 | rm -f $(DESTDIR)$(PREFIX)/bin/st 50 | rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 51 | 52 | .PHONY: all clean dist install uninstall 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2014-2022 Hiltjo Posthuma 4 | © 2018 Devin J. Pohly 5 | © 2014-2017 Quentin Rameau 6 | © 2009-2012 Aurélien APTEL 7 | © 2008-2017 Anselm R Garbe 8 | © 2012-2017 Roberto E. Vargas Caballero 9 | © 2012-2016 Christoph Lohmann <20h at r-36 dot net> 10 | © 2013 Eon S. Jeon 11 | © 2013 Alexander Sedov 12 | © 2013 Mark Edgar 13 | © 2013-2014 Eric Pruitt 14 | © 2013 Michael Forney 15 | © 2013-2014 Markus Teich 16 | © 2014-2015 Laslo Hunhold 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a 19 | copy of this software and associated documentation files (the "Software"), 20 | to deal in the Software without restriction, including without limitation 21 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 22 | and/or sell copies of the Software, and to permit persons to whom the 23 | Software is furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in 26 | all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 31 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 34 | DEALINGS IN THE SOFTWARE. 35 | -------------------------------------------------------------------------------- /st.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | 3 | #include 4 | #include 5 | 6 | /* macros */ 7 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 8 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 9 | #define LEN(a) (sizeof(a) / sizeof(a)[0]) 10 | #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 11 | #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) 12 | #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 13 | #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 14 | #define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ 15 | (a).fg != (b).fg || \ 16 | (a).bg != (b).bg) 17 | #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ 18 | (t1.tv_nsec-t2.tv_nsec)/1E6) 19 | #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) 20 | 21 | #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) 22 | #define IS_TRUECOL(x) (1 << 24 & (x)) 23 | #define HISTSIZE 2000 24 | 25 | enum glyph_attribute { 26 | ATTR_NULL = 0, 27 | ATTR_BOLD = 1 << 0, 28 | ATTR_FAINT = 1 << 1, 29 | ATTR_ITALIC = 1 << 2, 30 | ATTR_UNDERLINE = 1 << 3, 31 | ATTR_BLINK = 1 << 4, 32 | ATTR_REVERSE = 1 << 5, 33 | ATTR_INVISIBLE = 1 << 6, 34 | ATTR_STRUCK = 1 << 7, 35 | ATTR_WRAP = 1 << 8, 36 | ATTR_WIDE = 1 << 9, 37 | ATTR_WDUMMY = 1 << 10, 38 | ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 39 | }; 40 | 41 | enum selection_mode { 42 | SEL_IDLE = 0, 43 | SEL_EMPTY = 1, 44 | SEL_READY = 2 45 | }; 46 | 47 | enum selection_type { 48 | SEL_REGULAR = 1, 49 | SEL_RECTANGULAR = 2 50 | }; 51 | 52 | enum selection_snap { 53 | SNAP_WORD = 1, 54 | SNAP_LINE = 2 55 | }; 56 | 57 | typedef unsigned char uchar; 58 | typedef unsigned int uint; 59 | typedef unsigned long ulong; 60 | typedef unsigned short ushort; 61 | 62 | typedef uint_least32_t Rune; 63 | 64 | #define Glyph Glyph_ 65 | typedef struct { 66 | Rune u; /* character code */ 67 | ushort mode; /* attribute flags */ 68 | uint32_t fg; /* foreground */ 69 | uint32_t bg; /* background */ 70 | } Glyph; 71 | 72 | typedef Glyph *Line; 73 | 74 | typedef union { 75 | int i; 76 | uint ui; 77 | float f; 78 | const void *v; 79 | const char *s; 80 | } Arg; 81 | 82 | void die(const char *, ...); 83 | void redraw(void); 84 | void draw(void); 85 | 86 | void printscreen(const Arg *); 87 | void printsel(const Arg *); 88 | void sendbreak(const Arg *); 89 | void toggleprinter(const Arg *); 90 | 91 | int tattrset(int); 92 | void tnew(int, int); 93 | void tresize(int, int); 94 | void tsetdirtattr(int); 95 | void ttyhangup(void); 96 | int ttynew(const char *, char *, const char *, char **); 97 | size_t ttyread(void); 98 | void ttyresize(int, int); 99 | void ttywrite(const char *, size_t, int); 100 | 101 | void resettitle(void); 102 | 103 | void selclear(void); 104 | void selinit(void); 105 | void selstart(int, int, int); 106 | void selextend(int, int, int, int); 107 | int selected(int, int); 108 | char *getsel(void); 109 | 110 | size_t utf8encode(Rune, char *); 111 | 112 | void *xmalloc(size_t); 113 | void *xrealloc(void *, size_t); 114 | char *xstrdup(const char *); 115 | 116 | /* config.h globals */ 117 | extern char *utmp; 118 | extern char *scroll; 119 | extern char *stty_args; 120 | extern char *vtiden; 121 | extern wchar_t *worddelimiters; 122 | extern int allowaltscreen; 123 | extern int allowwindowops; 124 | extern char *termname; 125 | extern unsigned int tabspaces; 126 | extern unsigned int defaultfg; 127 | extern unsigned int defaultbg; 128 | extern unsigned int defaultcs; 129 | extern float alpha; 130 | extern float alpha_def; 131 | -------------------------------------------------------------------------------- /hb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "st.h" 10 | #include "hb.h" 11 | 12 | #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 } 13 | #define BUFFER_STEP 256 14 | 15 | hb_font_t *hbfindfont(XftFont *match); 16 | 17 | typedef struct { 18 | XftFont *match; 19 | hb_font_t *font; 20 | } HbFontMatch; 21 | 22 | typedef struct { 23 | size_t capacity; 24 | HbFontMatch *fonts; 25 | } HbFontCache; 26 | 27 | static HbFontCache hbfontcache = { 0, NULL }; 28 | 29 | typedef struct { 30 | size_t capacity; 31 | Rune *runes; 32 | } RuneBuffer; 33 | 34 | static RuneBuffer hbrunebuffer = { 0, NULL }; 35 | 36 | /* 37 | * Poplulate the array with a list of font features, wrapped in FEATURE macro, 38 | * e. g. 39 | * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') 40 | */ 41 | hb_feature_t features[] = { }; 42 | 43 | void 44 | hbunloadfonts() 45 | { 46 | for (int i = 0; i < hbfontcache.capacity; i++) { 47 | hb_font_destroy(hbfontcache.fonts[i].font); 48 | XftUnlockFace(hbfontcache.fonts[i].match); 49 | } 50 | 51 | if (hbfontcache.fonts != NULL) { 52 | free(hbfontcache.fonts); 53 | hbfontcache.fonts = NULL; 54 | } 55 | hbfontcache.capacity = 0; 56 | } 57 | 58 | hb_font_t * 59 | hbfindfont(XftFont *match) 60 | { 61 | for (int i = 0; i < hbfontcache.capacity; i++) { 62 | if (hbfontcache.fonts[i].match == match) 63 | return hbfontcache.fonts[i].font; 64 | } 65 | 66 | /* Font not found in cache, caching it now. */ 67 | hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); 68 | FT_Face face = XftLockFace(match); 69 | hb_font_t *font = hb_ft_font_create(face, NULL); 70 | if (font == NULL) 71 | die("Failed to load Harfbuzz font."); 72 | 73 | hbfontcache.fonts[hbfontcache.capacity].match = match; 74 | hbfontcache.fonts[hbfontcache.capacity].font = font; 75 | hbfontcache.capacity += 1; 76 | 77 | return font; 78 | } 79 | 80 | void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { 81 | ushort mode = USHRT_MAX; 82 | unsigned int glyph_count; 83 | int rune_idx, glyph_idx, end = start + length; 84 | 85 | hb_font_t *font = hbfindfont(xfont); 86 | if (font == NULL) 87 | return; 88 | 89 | hb_buffer_t *buffer = hb_buffer_create(); 90 | hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); 91 | hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); 92 | 93 | /* Resize the buffer if required length is larger. */ 94 | if (hbrunebuffer.capacity < length) { 95 | hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; 96 | hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); 97 | } 98 | 99 | /* Fill buffer with codepoints. */ 100 | for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { 101 | hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; 102 | mode = glyphs[glyph_idx].mode; 103 | if (mode & ATTR_WDUMMY) 104 | hbrunebuffer.runes[rune_idx] = 0x0020; 105 | } 106 | hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); 107 | 108 | /* Shape the segment. */ 109 | hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); 110 | 111 | /* Get new glyph info. */ 112 | hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); 113 | hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); 114 | 115 | /* Fill the output. */ 116 | data->buffer = buffer; 117 | data->glyphs = info; 118 | data->positions = pos; 119 | data->count = glyph_count; 120 | } 121 | 122 | void hbcleanup(HbTransformData *data) { 123 | hb_buffer_destroy(data->buffer); 124 | memset(data, 0, sizeof(HbTransformData)); 125 | } 126 | -------------------------------------------------------------------------------- /st.1: -------------------------------------------------------------------------------- 1 | .TH ST 1 st\-VERSION 2 | .SH NAME 3 | st \- simple terminal (bread's build) 4 | .SH SYNOPSIS 5 | .B st 6 | .RB [ \-aiv ] 7 | .RB [ \-c 8 | .IR class ] 9 | .RB [ \-f 10 | .IR font ] 11 | .RB [ \-g 12 | .IR geometry ] 13 | .RB [ \-n 14 | .IR name ] 15 | .RB [ \-o 16 | .IR iofile ] 17 | .RB [ \-T 18 | .IR title ] 19 | .RB [ \-t 20 | .IR title ] 21 | .RB [ \-l 22 | .IR line ] 23 | .RB [ \-w 24 | .IR windowid ] 25 | .RB [[ \-e ] 26 | .IR command 27 | .RI [ arguments ...]] 28 | .PP 29 | .B st 30 | .RB [ \-aiv ] 31 | .RB [ \-c 32 | .IR class ] 33 | .RB [ \-f 34 | .IR font ] 35 | .RB [ \-g 36 | .IR geometry ] 37 | .RB [ \-n 38 | .IR name ] 39 | .RB [ \-o 40 | .IR iofile ] 41 | .RB [ \-T 42 | .IR title ] 43 | .RB [ \-t 44 | .IR title ] 45 | .RB [ \-w 46 | .IR windowid ] 47 | .RB \-l 48 | .IR line 49 | .RI [ stty_args ...] 50 | .SH DESCRIPTION 51 | .B st 52 | is a simple terminal emulator. 53 | .SH OPTIONS 54 | .TP 55 | .B \-a 56 | disable alternate screens in terminal 57 | .TP 58 | .BI \-c " class" 59 | defines the window class (default $TERM). 60 | .TP 61 | .BI \-f " font" 62 | defines the 63 | .I font 64 | to use when st is run. 65 | .TP 66 | .BI \-g " geometry" 67 | defines the X11 geometry string. 68 | The form is [=][{xX}][{+-}{+-}]. See 69 | .BR XParseGeometry (3) 70 | for further details. 71 | .TP 72 | .B \-i 73 | will fixate the position given with the -g option. 74 | .TP 75 | .BI \-n " name" 76 | defines the window instance name (default $TERM). 77 | .TP 78 | .BI \-o " iofile" 79 | writes all the I/O to 80 | .I iofile. 81 | This feature is useful when recording st sessions. A value of "-" means 82 | standard output. 83 | .TP 84 | .BI \-T " title" 85 | defines the window title (default 'st'). 86 | .TP 87 | .BI \-t " title" 88 | defines the window title (default 'st'). 89 | .TP 90 | .BI \-w " windowid" 91 | embeds st within the window identified by 92 | .I windowid 93 | .TP 94 | .BI \-l " line" 95 | use a tty 96 | .I line 97 | instead of a pseudo terminal. 98 | .I line 99 | should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port 100 | 0). 101 | When this flag is given 102 | remaining arguments are used as flags for 103 | .BR stty(1). 104 | By default st initializes the serial line to 8 bits, no parity, 1 stop bit 105 | and a 38400 baud rate. The speed is set by appending it as last argument 106 | (e.g. 'st -l /dev/ttyS0 115200'). Arguments before the last one are 107 | .BR stty(1) 108 | flags. If you want to set odd parity on 115200 baud use for example 'st -l 109 | /dev/ttyS0 parenb parodd 115200'. Set the number of bits by using for 110 | example 'st -l /dev/ttyS0 cs7 115200'. See 111 | .BR stty(1) 112 | for more arguments and cases. 113 | .TP 114 | .B \-v 115 | prints version information to stderr, then exits. 116 | .TP 117 | .BI \-e " command " [ " arguments " "... ]" 118 | st executes 119 | .I command 120 | instead of the shell. If this is used it 121 | .B must be the last option 122 | on the command line, as in xterm / rxvt. 123 | This option is only intended for compatibility, 124 | and all the remaining arguments are used as a command 125 | even without it. 126 | .SH SHORTCUTS 127 | .TP 128 | .B Break 129 | Send a break in the serial line. 130 | Break key is obtained in PC keyboards 131 | pressing at the same time control and pause. 132 | .TP 133 | .B Ctrl-Print Screen 134 | Toggle if st should print to the 135 | .I iofile. 136 | .TP 137 | .B Shift-Print Screen 138 | Print the full screen to the 139 | .I iofile. 140 | .TP 141 | .B Print Screen 142 | Print the selection to the 143 | .I iofile. 144 | .TP 145 | .B Ctrl-Shift-Page Up 146 | Increase font size. 147 | .TP 148 | .B Ctrl-Shift-Page Down 149 | Decrease font size. 150 | .TP 151 | .B Ctrl-Shift-Home 152 | Reset to default font size. 153 | .TP 154 | .B Ctrl-Shift-y 155 | Paste from primary selection (middle mouse button). 156 | .TP 157 | .B Ctrl-Shift-c 158 | Copy the selected text to the clipboard selection. 159 | .TP 160 | .B Ctrl-Shift-v 161 | Paste from the clipboard selection. 162 | .SH CUSTOMIZATION 163 | .B st 164 | can be customized by creating a custom config.h and (re)compiling the source 165 | code. This keeps it fast, secure and simple. 166 | .SH AUTHORS 167 | See the LICENSE file for the authors. 168 | .SH LICENSE 169 | See the LICENSE file for the terms of redistribution. 170 | .SH SEE ALSO 171 | .BR tabbed (1), 172 | .BR utmp (1), 173 | .BR stty (1), 174 | .BR scroll (1) 175 | .SH BUGS 176 | See the TODO file in the distribution. 177 | 178 | -------------------------------------------------------------------------------- /st.info: -------------------------------------------------------------------------------- 1 | st-mono| simpleterm monocolor, 2 | acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 3 | am, 4 | bce, 5 | bel=^G, 6 | blink=\E[5m, 7 | bold=\E[1m, 8 | cbt=\E[Z, 9 | cvvis=\E[?25h, 10 | civis=\E[?25l, 11 | clear=\E[H\E[2J, 12 | cnorm=\E[?12l\E[?25h, 13 | colors#2, 14 | cols#80, 15 | cr=^M, 16 | csr=\E[%i%p1%d;%p2%dr, 17 | cub=\E[%p1%dD, 18 | cub1=^H, 19 | cud1=^J, 20 | cud=\E[%p1%dB, 21 | cuf1=\E[C, 22 | cuf=\E[%p1%dC, 23 | cup=\E[%i%p1%d;%p2%dH, 24 | cuu1=\E[A, 25 | cuu=\E[%p1%dA, 26 | dch=\E[%p1%dP, 27 | dch1=\E[P, 28 | dim=\E[2m, 29 | dl=\E[%p1%dM, 30 | dl1=\E[M, 31 | ech=\E[%p1%dX, 32 | ed=\E[J, 33 | el=\E[K, 34 | el1=\E[1K, 35 | enacs=\E)0, 36 | flash=\E[?5h$<80/>\E[?5l, 37 | fsl=^G, 38 | home=\E[H, 39 | hpa=\E[%i%p1%dG, 40 | hs, 41 | ht=^I, 42 | hts=\EH, 43 | ich=\E[%p1%d@, 44 | il1=\E[L, 45 | il=\E[%p1%dL, 46 | ind=^J, 47 | indn=\E[%p1%dS, 48 | invis=\E[8m, 49 | is2=\E[4l\E>\E[?1034l, 50 | it#8, 51 | kel=\E[1;2F, 52 | ked=\E[1;5F, 53 | ka1=\E[1~, 54 | ka3=\E[5~, 55 | kc1=\E[4~, 56 | kc3=\E[6~, 57 | kbs=\177, 58 | kcbt=\E[Z, 59 | kb2=\EOu, 60 | kcub1=\EOD, 61 | kcud1=\EOB, 62 | kcuf1=\EOC, 63 | kcuu1=\EOA, 64 | kDC=\E[3;2~, 65 | kent=\EOM, 66 | kEND=\E[1;2F, 67 | kIC=\E[2;2~, 68 | kNXT=\E[6;2~, 69 | kPRV=\E[5;2~, 70 | kHOM=\E[1;2H, 71 | kLFT=\E[1;2D, 72 | kRIT=\E[1;2C, 73 | kind=\E[1;2B, 74 | kri=\E[1;2A, 75 | kclr=\E[3;5~, 76 | kdl1=\E[3;2~, 77 | kdch1=\E[3~, 78 | kich1=\E[2~, 79 | kend=\E[4~, 80 | kf1=\EOP, 81 | kf2=\EOQ, 82 | kf3=\EOR, 83 | kf4=\EOS, 84 | kf5=\E[15~, 85 | kf6=\E[17~, 86 | kf7=\E[18~, 87 | kf8=\E[19~, 88 | kf9=\E[20~, 89 | kf10=\E[21~, 90 | kf11=\E[23~, 91 | kf12=\E[24~, 92 | kf13=\E[1;2P, 93 | kf14=\E[1;2Q, 94 | kf15=\E[1;2R, 95 | kf16=\E[1;2S, 96 | kf17=\E[15;2~, 97 | kf18=\E[17;2~, 98 | kf19=\E[18;2~, 99 | kf20=\E[19;2~, 100 | kf21=\E[20;2~, 101 | kf22=\E[21;2~, 102 | kf23=\E[23;2~, 103 | kf24=\E[24;2~, 104 | kf25=\E[1;5P, 105 | kf26=\E[1;5Q, 106 | kf27=\E[1;5R, 107 | kf28=\E[1;5S, 108 | kf29=\E[15;5~, 109 | kf30=\E[17;5~, 110 | kf31=\E[18;5~, 111 | kf32=\E[19;5~, 112 | kf33=\E[20;5~, 113 | kf34=\E[21;5~, 114 | kf35=\E[23;5~, 115 | kf36=\E[24;5~, 116 | kf37=\E[1;6P, 117 | kf38=\E[1;6Q, 118 | kf39=\E[1;6R, 119 | kf40=\E[1;6S, 120 | kf41=\E[15;6~, 121 | kf42=\E[17;6~, 122 | kf43=\E[18;6~, 123 | kf44=\E[19;6~, 124 | kf45=\E[20;6~, 125 | kf46=\E[21;6~, 126 | kf47=\E[23;6~, 127 | kf48=\E[24;6~, 128 | kf49=\E[1;3P, 129 | kf50=\E[1;3Q, 130 | kf51=\E[1;3R, 131 | kf52=\E[1;3S, 132 | kf53=\E[15;3~, 133 | kf54=\E[17;3~, 134 | kf55=\E[18;3~, 135 | kf56=\E[19;3~, 136 | kf57=\E[20;3~, 137 | kf58=\E[21;3~, 138 | kf59=\E[23;3~, 139 | kf60=\E[24;3~, 140 | kf61=\E[1;4P, 141 | kf62=\E[1;4Q, 142 | kf63=\E[1;4R, 143 | khome=\E[1~, 144 | kil1=\E[2;5~, 145 | krmir=\E[2;2~, 146 | knp=\E[6~, 147 | kmous=\E[M, 148 | kpp=\E[5~, 149 | lines#24, 150 | mir, 151 | msgr, 152 | npc, 153 | op=\E[39;49m, 154 | pairs#64, 155 | mc0=\E[i, 156 | mc4=\E[4i, 157 | mc5=\E[5i, 158 | rc=\E8, 159 | rev=\E[7m, 160 | ri=\EM, 161 | rin=\E[%p1%dT, 162 | ritm=\E[23m, 163 | rmacs=\E(B, 164 | rmcup=\E[?1049l, 165 | rmir=\E[4l, 166 | rmkx=\E[?1l\E>, 167 | rmso=\E[27m, 168 | rmul=\E[24m, 169 | rs1=\Ec, 170 | rs2=\E[4l\E>\E[?1034l, 171 | sc=\E7, 172 | sitm=\E[3m, 173 | sgr0=\E[0m, 174 | smacs=\E(0, 175 | smcup=\E[?1049h, 176 | smir=\E[4h, 177 | smkx=\E[?1h\E=, 178 | smso=\E[7m, 179 | smul=\E[4m, 180 | tbc=\E[3g, 181 | tsl=\E]0;, 182 | xenl, 183 | vpa=\E[%i%p1%dd, 184 | # XTerm extensions 185 | rmxx=\E[29m, 186 | smxx=\E[9m, 187 | BE=\E[?2004h, 188 | BD=\E[?2004l, 189 | PS=\E[200~, 190 | PE=\E[201~, 191 | # disabled rep for now: causes some issues with older ncurses versions. 192 | # rep=%p1%c\E[%p2%{1}%-%db, 193 | # tmux extensions, see TERMINFO EXTENSIONS in tmux(1) 194 | Tc, 195 | Ms=\E]52;%p1%s;%p2%s\007, 196 | Se=\E[2 q, 197 | Ss=\E[%p1%d q, 198 | 199 | st| simpleterm, 200 | use=st-mono, 201 | colors#8, 202 | setab=\E[4%p1%dm, 203 | setaf=\E[3%p1%dm, 204 | setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 205 | setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 206 | 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, 207 | 208 | st-256color| simpleterm with 256 colors, 209 | use=st, 210 | ccc, 211 | colors#256, 212 | oc=\E]104\007, 213 | pairs#32767, 214 | # Nicked from xterm-256color 215 | initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, 216 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 217 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 218 | 219 | st-meta| simpleterm with meta key, 220 | use=st, 221 | km, 222 | rmm=\E[?1034l, 223 | smm=\E[?1034h, 224 | rs2=\E[4l\E>\E[?1034h, 225 | is2=\E[4l\E>\E[?1034h, 226 | 227 | st-meta-256color| simpleterm with meta key and 256 colors, 228 | use=st-256color, 229 | km, 230 | rmm=\E[?1034l, 231 | smm=\E[?1034h, 232 | rs2=\E[4l\E>\E[?1034h, 233 | is2=\E[4l\E>\E[?1034h, 234 | 235 | st-bs| simpleterm with backspace as backspace, 236 | use=st, 237 | kbs=\010, 238 | kdch1=\177, 239 | 240 | st-bs-256color| simpleterm with backspace as backspace and 256colors, 241 | use=st-256color, 242 | kbs=\010, 243 | kdch1=\177, 244 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | /* 4 | * appearance 5 | * 6 | * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html 7 | */ 8 | static char *font = "mono:pixelsize=17:antialias=true:autohint=true"; 9 | static int borderpx = 2; 10 | 11 | /* 12 | * What program is execed by st depends of these precedence rules: 13 | * 1: program passed with -e 14 | * 2: scroll and/or utmp 15 | * 3: SHELL environment variable 16 | * 4: value of shell in /etc/passwd 17 | * 5: value of shell in config.h 18 | */ 19 | static char *shell = "/bin/sh"; 20 | char *utmp = NULL; 21 | /* scroll program: to enable use a string like "scroll" */ 22 | char *scroll = NULL; 23 | char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; 24 | 25 | /* identification sequence returned in DA and DECID */ 26 | char *vtiden = "\033[?6c"; 27 | 28 | /* Kerning / character bounding-box multipliers */ 29 | static float cwscale = 1.0; 30 | static float chscale = 1.0; 31 | 32 | /* 33 | * word delimiter string 34 | * 35 | * More advanced example: L" `'\"()[]{}" 36 | */ 37 | wchar_t *worddelimiters = L" "; 38 | 39 | /* selection timeouts (in milliseconds) */ 40 | static unsigned int doubleclicktimeout = 300; 41 | static unsigned int tripleclicktimeout = 600; 42 | 43 | /* alt screens */ 44 | int allowaltscreen = 1; 45 | 46 | /* allow certain non-interactive (insecure) window operations such as: 47 | setting the clipboard text */ 48 | int allowwindowops = 0; 49 | 50 | /* 51 | * draw latency range in ms - from new content/keypress/etc until drawing. 52 | * within this range, st draws when content stops arriving (idle). mostly it's 53 | * near minlatency, but it waits longer for slow updates to avoid partial draw. 54 | * low minlatency will tear/flicker more, as it can "detect" idle too early. 55 | */ 56 | static double minlatency = 2; 57 | static double maxlatency = 33; 58 | 59 | /* 60 | * blinking timeout (set to 0 to disable blinking) for the terminal blinking 61 | * attribute. 62 | */ 63 | static unsigned int blinktimeout = 800; 64 | 65 | /* 66 | * thickness of underline and bar cursors 67 | */ 68 | static unsigned int cursorthickness = 2; 69 | 70 | /* 71 | * bell volume. It must be a value between -100 and 100. Use 0 for disabling 72 | * it 73 | */ 74 | static int bellvolume = 0; 75 | 76 | /* default TERM value */ 77 | char *termname = "st-256color"; 78 | 79 | /* 80 | * spaces per tab 81 | * 82 | * When you are changing this value, don't forget to adapt the »it« value in 83 | * the st.info and appropriately install the st.info in the environment where 84 | * you use this st version. 85 | * 86 | * it#$tabspaces, 87 | * 88 | * Secondly make sure your kernel is not expanding tabs. When running `stty 89 | * -a` »tab0« should appear. You can tell the terminal to not expand tabs by 90 | * running following command: 91 | * 92 | * stty tabs 93 | */ 94 | unsigned int tabspaces = 8; 95 | 96 | /* bg opacity */ 97 | float alpha = 0.8; 98 | 99 | /* Background opacity */ 100 | float alpha_def; 101 | 102 | /* Terminal colors (16 first used in escape sequence) */ 103 | static const char *colorname[] = { 104 | // Nord Color Scheme 105 | /* 8 normal colors */ 106 | "#3b4252", /* black */ 107 | "#bf616a", /* red */ 108 | "#a3be8c", /* green */ 109 | "#ebcb8b", /* yellow */ 110 | "#81a1c1", /* blue */ 111 | "#b48ead", /* magenta */ 112 | "#88c0d0", /* cyan */ 113 | "#e5e9f0", /* white */ 114 | 115 | /* 8 bright colors */ 116 | "#4c566a", /* black */ 117 | "#bf616a", /* red */ 118 | "#a3be8c", /* green */ 119 | "#ebcb8b", /* yellow */ 120 | "#81a1c1", /* blue */ 121 | "#b48ead", /* magenta */ 122 | "#8fbcbb", /* cyan */ 123 | "#eceff4", /* white */ 124 | 125 | [255] = 0, 126 | 127 | /* more colors can be added after 255 to use with DefaultXX */ 128 | "#d8dee9", /* default foreground colour */ 129 | "#2e3440", /* default background colour */ 130 | }; 131 | 132 | 133 | /* 134 | * Default colors (colorname index) 135 | * foreground, background, cursor, reverse cursor 136 | */ 137 | unsigned int defaultfg = 256; 138 | unsigned int defaultbg = 257; 139 | unsigned int defaultcs = 256; 140 | static unsigned int defaultrcs = 257; 141 | 142 | /* 143 | * Default shape of cursor 144 | * 2: Block ("█") 145 | * 4: Underline ("_") 146 | * 6: Bar ("|") 147 | * 7: Snowman ("☃") 148 | */ 149 | static unsigned int cursorshape = 6; 150 | 151 | /* 152 | * Default columns and rows numbers 153 | */ 154 | 155 | static unsigned int cols = 80; 156 | static unsigned int rows = 24; 157 | 158 | /* 159 | * Default colour and shape of the mouse cursor 160 | */ 161 | static unsigned int mouseshape = XC_xterm; 162 | static unsigned int mousefg = 7; 163 | static unsigned int mousebg = 0; 164 | 165 | /* 166 | * Color used to display font attributes when fontconfig selected a font which 167 | * doesn't match the ones requested. 168 | */ 169 | static unsigned int defaultattr = 11; 170 | 171 | /* 172 | * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). 173 | * Note that if you want to use ShiftMask with selmasks, set this to an other 174 | * modifier, set to 0 to not use it. 175 | */ 176 | static uint forcemousemod = ShiftMask; 177 | 178 | /* 179 | * Internal mouse shortcuts. 180 | * Beware that overloading Button1 will disable the selection. 181 | */ 182 | static MouseShortcut mshortcuts[] = { 183 | /* mask button function argument release */ 184 | { XK_NO_MOD, Button4, kscrollup, {.i = 1} }, 185 | { XK_NO_MOD, Button5, kscrolldown, {.i = 1} }, 186 | { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, 187 | { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, 188 | { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, 189 | { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, 190 | { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, 191 | }; 192 | 193 | /* Internal keyboard shortcuts. */ 194 | #define MODKEY Mod1Mask 195 | #define TERMMOD (Mod1Mask|ShiftMask) 196 | 197 | static Shortcut shortcuts[] = { 198 | /* mask keysym function argument */ 199 | { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 200 | { ControlMask, XK_Print, toggleprinter, {.i = 0} }, 201 | { ShiftMask, XK_Print, printscreen, {.i = 0} }, 202 | { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, 203 | { TERMMOD, XK_K, zoom, {.f = +1} }, 204 | { TERMMOD, XK_J, zoom, {.f = -1} }, 205 | { TERMMOD, XK_Home, zoomreset, {.f = 0} }, 206 | { MODKEY, XK_c, clipcopy, {.i = 0} }, 207 | { MODKEY, XK_v, clippaste, {.i = 0} }, 208 | { TERMMOD, XK_Y, selpaste, {.i = 0} }, 209 | { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 210 | { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 211 | { MODKEY, XK_k, kscrollup, {.i = 1} }, 212 | { MODKEY, XK_j, kscrolldown, {.i = 1} }, 213 | { MODKEY, XK_a, chgalpha, {.f = -1} }, 214 | { MODKEY, XK_s, chgalpha, {.f = +1} }, 215 | { MODKEY, XK_d, chgalpha, {.f = 0} }, 216 | }; 217 | 218 | /* 219 | * Special keys (change & recompile st.info accordingly) 220 | * 221 | * Mask value: 222 | * * Use XK_ANY_MOD to match the key no matter modifiers state 223 | * * Use XK_NO_MOD to match the key alone (no modifiers) 224 | * appkey value: 225 | * * 0: no value 226 | * * > 0: keypad application mode enabled 227 | * * = 2: term.numlock = 1 228 | * * < 0: keypad application mode disabled 229 | * appcursor value: 230 | * * 0: no value 231 | * * > 0: cursor application mode enabled 232 | * * < 0: cursor application mode disabled 233 | * 234 | * Be careful with the order of the definitions because st searches in 235 | * this table sequentially, so any XK_ANY_MOD must be in the last 236 | * position for a key. 237 | */ 238 | 239 | /* 240 | * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) 241 | * to be mapped below, add them to this array. 242 | */ 243 | static KeySym mappedkeys[] = { -1 }; 244 | 245 | /* 246 | * State bits to ignore when matching key or button events. By default, 247 | * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. 248 | */ 249 | static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; 250 | 251 | /* 252 | * This is the huge key array which defines all compatibility to the Linux 253 | * world. Please decide about changes wisely. 254 | */ 255 | static Key key[] = { 256 | /* keysym mask string appkey appcursor */ 257 | { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, 258 | { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, 259 | { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, 260 | { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 261 | { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, 262 | { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, 263 | { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, 264 | { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, 265 | { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, 266 | { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, 267 | { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, 268 | { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, 269 | { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, 270 | { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, 271 | { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, 272 | { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, 273 | { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, 274 | { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 275 | { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, 276 | { XK_KP_End, ControlMask, "\033[J", -1, 0}, 277 | { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, 278 | { XK_KP_End, ShiftMask, "\033[K", -1, 0}, 279 | { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, 280 | { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, 281 | { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, 282 | { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 283 | { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, 284 | { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, 285 | { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, 286 | { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, 287 | { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 288 | { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 289 | { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, 290 | { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, 291 | { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, 292 | { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, 293 | { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 294 | { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 295 | { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, 296 | { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, 297 | { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, 298 | { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, 299 | { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, 300 | { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, 301 | { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, 302 | { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, 303 | { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, 304 | { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, 305 | { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, 306 | { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, 307 | { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, 308 | { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, 309 | { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, 310 | { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, 311 | { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, 312 | { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, 313 | { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, 314 | { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, 315 | { XK_Up, ControlMask, "\033[1;5A", 0, 0}, 316 | { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, 317 | { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, 318 | { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, 319 | { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, 320 | { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, 321 | { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, 322 | { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, 323 | { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, 324 | { XK_Down, ControlMask, "\033[1;5B", 0, 0}, 325 | { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, 326 | { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, 327 | { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, 328 | { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, 329 | { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, 330 | { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, 331 | { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, 332 | { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, 333 | { XK_Left, ControlMask, "\033[1;5D", 0, 0}, 334 | { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, 335 | { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, 336 | { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, 337 | { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, 338 | { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, 339 | { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, 340 | { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, 341 | { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, 342 | { XK_Right, ControlMask, "\033[1;5C", 0, 0}, 343 | { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, 344 | { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, 345 | { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, 346 | { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, 347 | { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, 348 | { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, 349 | { XK_Return, Mod1Mask, "\033\r", 0, 0}, 350 | { XK_Return, XK_ANY_MOD, "\r", 0, 0}, 351 | { XK_Insert, ShiftMask, "\033[4l", -1, 0}, 352 | { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, 353 | { XK_Insert, ControlMask, "\033[L", -1, 0}, 354 | { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, 355 | { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 356 | { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 357 | { XK_Delete, ControlMask, "\033[M", -1, 0}, 358 | { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, 359 | { XK_Delete, ShiftMask, "\033[2K", -1, 0}, 360 | { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, 361 | { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 362 | { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 363 | { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, 364 | { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, 365 | { XK_Home, ShiftMask, "\033[2J", 0, -1}, 366 | { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, 367 | { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, 368 | { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 369 | { XK_End, ControlMask, "\033[J", -1, 0}, 370 | { XK_End, ControlMask, "\033[1;5F", +1, 0}, 371 | { XK_End, ShiftMask, "\033[K", -1, 0}, 372 | { XK_End, ShiftMask, "\033[1;2F", +1, 0}, 373 | { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, 374 | { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, 375 | { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, 376 | { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 377 | { XK_Next, ControlMask, "\033[6;5~", 0, 0}, 378 | { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, 379 | { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 380 | { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, 381 | { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, 382 | { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, 383 | { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, 384 | { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, 385 | { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, 386 | { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, 387 | { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, 388 | { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, 389 | { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, 390 | { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, 391 | { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, 392 | { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, 393 | { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, 394 | { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, 395 | { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, 396 | { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, 397 | { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, 398 | { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, 399 | { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, 400 | { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, 401 | { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, 402 | { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, 403 | { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, 404 | { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, 405 | { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, 406 | { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, 407 | { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, 408 | { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, 409 | { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, 410 | { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, 411 | { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, 412 | { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, 413 | { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, 414 | { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, 415 | { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, 416 | { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, 417 | { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, 418 | { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, 419 | { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, 420 | { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, 421 | { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, 422 | { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, 423 | { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, 424 | { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, 425 | { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, 426 | { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, 427 | { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, 428 | { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, 429 | { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, 430 | { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, 431 | { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, 432 | { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, 433 | { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, 434 | { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, 435 | { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, 436 | { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, 437 | { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, 438 | { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, 439 | { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, 440 | { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, 441 | { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, 442 | { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, 443 | { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, 444 | { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, 445 | { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, 446 | { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, 447 | { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, 448 | { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, 449 | { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, 450 | { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, 451 | { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, 452 | { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, 453 | { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, 454 | { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, 455 | { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, 456 | { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, 457 | { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, 458 | { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, 459 | { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, 460 | { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, 461 | { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, 462 | { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, 463 | { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, 464 | { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, 465 | { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, 466 | }; 467 | 468 | /* 469 | * Selection types' masks. 470 | * Use the same masks as usual. 471 | * Button1Mask is always unset, to make masks match between ButtonPress. 472 | * ButtonRelease and MotionNotify. 473 | * If no match is found, regular selection is used. 474 | */ 475 | static uint selmasks[] = { 476 | [SEL_RECTANGULAR] = Mod1Mask, 477 | }; 478 | 479 | /* 480 | * Printable characters in ASCII, used to estimate the advance width 481 | * of single wide characters. 482 | */ 483 | static char ascii_printable[] = 484 | " !\"#$%&'()*+,-./0123456789:;<=>?" 485 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 486 | "`abcdefghijklmnopqrstuvwxyz{|}~"; 487 | -------------------------------------------------------------------------------- /x.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | char *argv0; 20 | #include "arg.h" 21 | #include "st.h" 22 | #include "win.h" 23 | #include "hb.h" 24 | 25 | /* types used in config.h */ 26 | typedef struct { 27 | uint mod; 28 | KeySym keysym; 29 | void (*func)(const Arg *); 30 | const Arg arg; 31 | } Shortcut; 32 | 33 | typedef struct { 34 | uint mod; 35 | uint button; 36 | void (*func)(const Arg *); 37 | const Arg arg; 38 | uint release; 39 | } MouseShortcut; 40 | 41 | typedef struct { 42 | KeySym k; 43 | uint mask; 44 | char *s; 45 | /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ 46 | signed char appkey; /* application keypad */ 47 | signed char appcursor; /* application cursor */ 48 | } Key; 49 | 50 | /* X modifiers */ 51 | #define XK_ANY_MOD UINT_MAX 52 | #define XK_NO_MOD 0 53 | #define XK_SWITCH_MOD (1<<13|1<<14) 54 | 55 | /* function definitions used in config.h */ 56 | static void clipcopy(const Arg *); 57 | static void clippaste(const Arg *); 58 | static void numlock(const Arg *); 59 | static void selpaste(const Arg *); 60 | static void zoom(const Arg *); 61 | static void zoomabs(const Arg *); 62 | static void zoomreset(const Arg *); 63 | static void ttysend(const Arg *); 64 | static void chgalpha(const Arg *); 65 | void kscrollup(const Arg *); 66 | void kscrolldown(const Arg *); 67 | 68 | /* config.h for applying patches and the configuration. */ 69 | #include "config.h" 70 | 71 | /* XEMBED messages */ 72 | #define XEMBED_FOCUS_IN 4 73 | #define XEMBED_FOCUS_OUT 5 74 | 75 | /* macros */ 76 | #define IS_SET(flag) ((win.mode & (flag)) != 0) 77 | #define TRUERED(x) (((x) & 0xff0000) >> 8) 78 | #define TRUEGREEN(x) (((x) & 0xff00)) 79 | #define TRUEBLUE(x) (((x) & 0xff) << 8) 80 | 81 | typedef XftDraw *Draw; 82 | typedef XftColor Color; 83 | typedef XftGlyphFontSpec GlyphFontSpec; 84 | 85 | /* Purely graphic info */ 86 | typedef struct { 87 | int tw, th; /* tty width and height */ 88 | int w, h; /* window width and height */ 89 | int ch; /* char height */ 90 | int cw; /* char width */ 91 | int mode; /* window state/mode flags */ 92 | int cursor; /* cursor style */ 93 | } TermWindow; 94 | 95 | typedef struct { 96 | Display *dpy; 97 | Colormap cmap; 98 | Window win; 99 | Drawable buf; 100 | GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ 101 | Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; 102 | struct { 103 | XIM xim; 104 | XIC xic; 105 | XPoint spot; 106 | XVaNestedList spotlist; 107 | } ime; 108 | Draw draw; 109 | Visual *vis; 110 | XSetWindowAttributes attrs; 111 | int scr; 112 | int isfixed; /* is fixed geometry? */ 113 | int depth; /* bit depth */ 114 | int l, t; /* left and top offset */ 115 | int gm; /* geometry mask */ 116 | } XWindow; 117 | 118 | typedef struct { 119 | Atom xtarget; 120 | char *primary, *clipboard; 121 | struct timespec tclick1; 122 | struct timespec tclick2; 123 | } XSelection; 124 | 125 | /* Font structure */ 126 | #define Font Font_ 127 | typedef struct { 128 | int height; 129 | int width; 130 | int ascent; 131 | int descent; 132 | int badslant; 133 | int badweight; 134 | short lbearing; 135 | short rbearing; 136 | XftFont *match; 137 | FcFontSet *set; 138 | FcPattern *pattern; 139 | } Font; 140 | 141 | /* Drawing Context */ 142 | typedef struct { 143 | Color *col; 144 | size_t collen; 145 | Font font, bfont, ifont, ibfont; 146 | GC gc; 147 | unsigned short bg_alpha; 148 | } DC; 149 | 150 | static inline ushort sixd_to_16bit(int); 151 | static void xresetfontsettings(ushort mode, Font **font, int *frcflags); 152 | static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 153 | static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); 154 | static void xdrawglyph(Glyph, int, int); 155 | static void xclear(int, int, int, int); 156 | static int xgeommasktogravity(int); 157 | static int ximopen(Display *); 158 | static void ximinstantiate(Display *, XPointer, XPointer); 159 | static void ximdestroy(XIM, XPointer, XPointer); 160 | static int xicdestroy(XIC, XPointer, XPointer); 161 | static void xinit(int, int); 162 | static void cresize(int, int); 163 | static void xresize(int, int); 164 | static void xhints(void); 165 | static int xloadcolor(int, const char *, Color *); 166 | static int xloadfont(Font *, FcPattern *); 167 | static void xloadfonts(const char *, double); 168 | static void xunloadfont(Font *); 169 | static void xunloadfonts(void); 170 | static void xsetenv(void); 171 | static void xseturgency(int); 172 | static int evcol(XEvent *); 173 | static int evrow(XEvent *); 174 | 175 | static void expose(XEvent *); 176 | static void visibility(XEvent *); 177 | static void unmap(XEvent *); 178 | static void kpress(XEvent *); 179 | static void cmessage(XEvent *); 180 | static void resize(XEvent *); 181 | static void focus(XEvent *); 182 | static uint buttonmask(uint); 183 | static int mouseaction(XEvent *, uint); 184 | static void brelease(XEvent *); 185 | static void bpress(XEvent *); 186 | static void bmotion(XEvent *); 187 | static void propnotify(XEvent *); 188 | static void selnotify(XEvent *); 189 | static void selclear_(XEvent *); 190 | static void selrequest(XEvent *); 191 | static void setsel(char *, Time); 192 | static void mousesel(XEvent *, int); 193 | static void mousereport(XEvent *); 194 | static char *kmap(KeySym, uint); 195 | static int match(uint, uint); 196 | 197 | static void run(void); 198 | static void usage(void); 199 | 200 | static void (*handler[LASTEvent])(XEvent *) = { 201 | [KeyPress] = kpress, 202 | [ClientMessage] = cmessage, 203 | [ConfigureNotify] = resize, 204 | [VisibilityNotify] = visibility, 205 | [UnmapNotify] = unmap, 206 | [Expose] = expose, 207 | [FocusIn] = focus, 208 | [FocusOut] = focus, 209 | [MotionNotify] = bmotion, 210 | [ButtonPress] = bpress, 211 | [ButtonRelease] = brelease, 212 | /* 213 | * Uncomment if you want the selection to disappear when you select something 214 | * different in another window. 215 | */ 216 | /* [SelectionClear] = selclear_, */ 217 | [SelectionNotify] = selnotify, 218 | /* 219 | * PropertyNotify is only turned on when there is some INCR transfer happening 220 | * for the selection retrieval. 221 | */ 222 | [PropertyNotify] = propnotify, 223 | [SelectionRequest] = selrequest, 224 | }; 225 | 226 | /* Globals */ 227 | static DC dc; 228 | static XWindow xw; 229 | static XSelection xsel; 230 | static TermWindow win; 231 | 232 | /* Font Ring Cache */ 233 | enum { 234 | FRC_NORMAL, 235 | FRC_ITALIC, 236 | FRC_BOLD, 237 | FRC_ITALICBOLD 238 | }; 239 | 240 | typedef struct { 241 | XftFont *font; 242 | int flags; 243 | Rune unicodep; 244 | } Fontcache; 245 | 246 | /* Fontcache is an array now. A new font will be appended to the array. */ 247 | static Fontcache *frc = NULL; 248 | static int frclen = 0; 249 | static int frccap = 0; 250 | static char *usedfont = NULL; 251 | static double usedfontsize = 0; 252 | static double defaultfontsize = 0; 253 | 254 | static char *opt_alpha = NULL; 255 | static char *opt_class = NULL; 256 | static char **opt_cmd = NULL; 257 | static char *opt_embed = NULL; 258 | static char *opt_font = NULL; 259 | static char *opt_io = NULL; 260 | static char *opt_line = NULL; 261 | static char *opt_name = NULL; 262 | static char *opt_title = NULL; 263 | 264 | static uint buttons; /* bit field of pressed buttons */ 265 | 266 | void 267 | clipcopy(const Arg *dummy) 268 | { 269 | Atom clipboard; 270 | 271 | free(xsel.clipboard); 272 | xsel.clipboard = NULL; 273 | 274 | if (xsel.primary != NULL) { 275 | xsel.clipboard = xstrdup(xsel.primary); 276 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 277 | XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); 278 | } 279 | } 280 | 281 | void 282 | clippaste(const Arg *dummy) 283 | { 284 | Atom clipboard; 285 | 286 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 287 | XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, 288 | xw.win, CurrentTime); 289 | } 290 | 291 | void 292 | selpaste(const Arg *dummy) 293 | { 294 | XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, 295 | xw.win, CurrentTime); 296 | } 297 | 298 | void 299 | numlock(const Arg *dummy) 300 | { 301 | win.mode ^= MODE_NUMLOCK; 302 | } 303 | 304 | void 305 | zoom(const Arg *arg) 306 | { 307 | Arg larg; 308 | 309 | larg.f = usedfontsize + arg->f; 310 | zoomabs(&larg); 311 | } 312 | 313 | void 314 | zoomabs(const Arg *arg) 315 | { 316 | xunloadfonts(); 317 | xloadfonts(usedfont, arg->f); 318 | cresize(0, 0); 319 | redraw(); 320 | xhints(); 321 | } 322 | 323 | void 324 | zoomreset(const Arg *arg) 325 | { 326 | Arg larg; 327 | 328 | if (defaultfontsize > 0) { 329 | larg.f = defaultfontsize; 330 | zoomabs(&larg); 331 | } 332 | } 333 | 334 | void 335 | ttysend(const Arg *arg) 336 | { 337 | ttywrite(arg->s, strlen(arg->s), 1); 338 | } 339 | 340 | int 341 | evcol(XEvent *e) 342 | { 343 | int x = e->xbutton.x - borderpx; 344 | LIMIT(x, 0, win.tw - 1); 345 | return x / win.cw; 346 | } 347 | 348 | int 349 | evrow(XEvent *e) 350 | { 351 | int y = e->xbutton.y - borderpx; 352 | LIMIT(y, 0, win.th - 1); 353 | return y / win.ch; 354 | } 355 | 356 | void 357 | mousesel(XEvent *e, int done) 358 | { 359 | int type, seltype = SEL_REGULAR; 360 | uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); 361 | 362 | for (type = 1; type < LEN(selmasks); ++type) { 363 | if (match(selmasks[type], state)) { 364 | seltype = type; 365 | break; 366 | } 367 | } 368 | selextend(evcol(e), evrow(e), seltype, done); 369 | if (done) 370 | setsel(getsel(), e->xbutton.time); 371 | } 372 | 373 | void 374 | mousereport(XEvent *e) 375 | { 376 | int len, btn, code; 377 | int x = evcol(e), y = evrow(e); 378 | int state = e->xbutton.state; 379 | char buf[40]; 380 | static int ox, oy; 381 | 382 | if (e->type == MotionNotify) { 383 | if (x == ox && y == oy) 384 | return; 385 | if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) 386 | return; 387 | /* MODE_MOUSEMOTION: no reporting if no button is pressed */ 388 | if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) 389 | return; 390 | /* Set btn to lowest-numbered pressed button, or 12 if no 391 | * buttons are pressed. */ 392 | for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) 393 | ; 394 | code = 32; 395 | } else { 396 | btn = e->xbutton.button; 397 | /* Only buttons 1 through 11 can be encoded */ 398 | if (btn < 1 || btn > 11) 399 | return; 400 | if (e->type == ButtonRelease) { 401 | /* MODE_MOUSEX10: no button release reporting */ 402 | if (IS_SET(MODE_MOUSEX10)) 403 | return; 404 | /* Don't send release events for the scroll wheel */ 405 | if (btn == 4 || btn == 5) 406 | return; 407 | } 408 | code = 0; 409 | } 410 | 411 | ox = x; 412 | oy = y; 413 | 414 | /* Encode btn into code. If no button is pressed for a motion event in 415 | * MODE_MOUSEMANY, then encode it as a release. */ 416 | if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) 417 | code += 3; 418 | else if (btn >= 8) 419 | code += 128 + btn - 8; 420 | else if (btn >= 4) 421 | code += 64 + btn - 4; 422 | else 423 | code += btn - 1; 424 | 425 | if (!IS_SET(MODE_MOUSEX10)) { 426 | code += ((state & ShiftMask ) ? 4 : 0) 427 | + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ 428 | + ((state & ControlMask) ? 16 : 0); 429 | } 430 | 431 | if (IS_SET(MODE_MOUSESGR)) { 432 | len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", 433 | code, x+1, y+1, 434 | e->type == ButtonRelease ? 'm' : 'M'); 435 | } else if (x < 223 && y < 223) { 436 | len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", 437 | 32+code, 32+x+1, 32+y+1); 438 | } else { 439 | return; 440 | } 441 | 442 | ttywrite(buf, len, 0); 443 | } 444 | 445 | uint 446 | buttonmask(uint button) 447 | { 448 | return button == Button1 ? Button1Mask 449 | : button == Button2 ? Button2Mask 450 | : button == Button3 ? Button3Mask 451 | : button == Button4 ? Button4Mask 452 | : button == Button5 ? Button5Mask 453 | : 0; 454 | } 455 | 456 | int 457 | mouseaction(XEvent *e, uint release) 458 | { 459 | MouseShortcut *ms; 460 | 461 | /* ignore Buttonmask for Button - it's set on release */ 462 | uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); 463 | 464 | for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { 465 | if (ms->release == release && 466 | ms->button == e->xbutton.button && 467 | (match(ms->mod, state) || /* exact or forced */ 468 | match(ms->mod, state & ~forcemousemod))) { 469 | ms->func(&(ms->arg)); 470 | return 1; 471 | } 472 | } 473 | 474 | return 0; 475 | } 476 | 477 | void 478 | bpress(XEvent *e) 479 | { 480 | int btn = e->xbutton.button; 481 | struct timespec now; 482 | int snap; 483 | 484 | if (1 <= btn && btn <= 11) 485 | buttons |= 1 << (btn-1); 486 | 487 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 488 | mousereport(e); 489 | return; 490 | } 491 | 492 | if (mouseaction(e, 0)) 493 | return; 494 | 495 | if (btn == Button1) { 496 | /* 497 | * If the user clicks below predefined timeouts specific 498 | * snapping behaviour is exposed. 499 | */ 500 | clock_gettime(CLOCK_MONOTONIC, &now); 501 | if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { 502 | snap = SNAP_LINE; 503 | } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { 504 | snap = SNAP_WORD; 505 | } else { 506 | snap = 0; 507 | } 508 | xsel.tclick2 = xsel.tclick1; 509 | xsel.tclick1 = now; 510 | 511 | selstart(evcol(e), evrow(e), snap); 512 | } 513 | } 514 | 515 | void 516 | propnotify(XEvent *e) 517 | { 518 | XPropertyEvent *xpev; 519 | Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 520 | 521 | xpev = &e->xproperty; 522 | if (xpev->state == PropertyNewValue && 523 | (xpev->atom == XA_PRIMARY || 524 | xpev->atom == clipboard)) { 525 | selnotify(e); 526 | } 527 | } 528 | 529 | void 530 | selnotify(XEvent *e) 531 | { 532 | ulong nitems, ofs, rem; 533 | int format; 534 | uchar *data, *last, *repl; 535 | Atom type, incratom, property = None; 536 | 537 | incratom = XInternAtom(xw.dpy, "INCR", 0); 538 | 539 | ofs = 0; 540 | if (e->type == SelectionNotify) 541 | property = e->xselection.property; 542 | else if (e->type == PropertyNotify) 543 | property = e->xproperty.atom; 544 | 545 | if (property == None) 546 | return; 547 | 548 | do { 549 | if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, 550 | BUFSIZ/4, False, AnyPropertyType, 551 | &type, &format, &nitems, &rem, 552 | &data)) { 553 | fprintf(stderr, "Clipboard allocation failed\n"); 554 | return; 555 | } 556 | 557 | if (e->type == PropertyNotify && nitems == 0 && rem == 0) { 558 | /* 559 | * If there is some PropertyNotify with no data, then 560 | * this is the signal of the selection owner that all 561 | * data has been transferred. We won't need to receive 562 | * PropertyNotify events anymore. 563 | */ 564 | MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); 565 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 566 | &xw.attrs); 567 | } 568 | 569 | if (type == incratom) { 570 | /* 571 | * Activate the PropertyNotify events so we receive 572 | * when the selection owner does send us the next 573 | * chunk of data. 574 | */ 575 | MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); 576 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 577 | &xw.attrs); 578 | 579 | /* 580 | * Deleting the property is the transfer start signal. 581 | */ 582 | XDeleteProperty(xw.dpy, xw.win, (int)property); 583 | continue; 584 | } 585 | 586 | /* 587 | * As seen in getsel: 588 | * Line endings are inconsistent in the terminal and GUI world 589 | * copy and pasting. When receiving some selection data, 590 | * replace all '\n' with '\r'. 591 | * FIXME: Fix the computer world. 592 | */ 593 | repl = data; 594 | last = data + nitems * format / 8; 595 | while ((repl = memchr(repl, '\n', last - repl))) { 596 | *repl++ = '\r'; 597 | } 598 | 599 | if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) 600 | ttywrite("\033[200~", 6, 0); 601 | ttywrite((char *)data, nitems * format / 8, 1); 602 | if (IS_SET(MODE_BRCKTPASTE) && rem == 0) 603 | ttywrite("\033[201~", 6, 0); 604 | XFree(data); 605 | /* number of 32-bit chunks returned */ 606 | ofs += nitems * format / 32; 607 | } while (rem > 0); 608 | 609 | /* 610 | * Deleting the property again tells the selection owner to send the 611 | * next data chunk in the property. 612 | */ 613 | XDeleteProperty(xw.dpy, xw.win, (int)property); 614 | } 615 | 616 | void 617 | xclipcopy(void) 618 | { 619 | clipcopy(NULL); 620 | } 621 | 622 | void 623 | selclear_(XEvent *e) 624 | { 625 | selclear(); 626 | } 627 | 628 | void 629 | selrequest(XEvent *e) 630 | { 631 | XSelectionRequestEvent *xsre; 632 | XSelectionEvent xev; 633 | Atom xa_targets, string, clipboard; 634 | char *seltext; 635 | 636 | xsre = (XSelectionRequestEvent *) e; 637 | xev.type = SelectionNotify; 638 | xev.requestor = xsre->requestor; 639 | xev.selection = xsre->selection; 640 | xev.target = xsre->target; 641 | xev.time = xsre->time; 642 | if (xsre->property == None) 643 | xsre->property = xsre->target; 644 | 645 | /* reject */ 646 | xev.property = None; 647 | 648 | xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); 649 | if (xsre->target == xa_targets) { 650 | /* respond with the supported type */ 651 | string = xsel.xtarget; 652 | XChangeProperty(xsre->display, xsre->requestor, xsre->property, 653 | XA_ATOM, 32, PropModeReplace, 654 | (uchar *) &string, 1); 655 | xev.property = xsre->property; 656 | } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { 657 | /* 658 | * xith XA_STRING non ascii characters may be incorrect in the 659 | * requestor. It is not our problem, use utf8. 660 | */ 661 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 662 | if (xsre->selection == XA_PRIMARY) { 663 | seltext = xsel.primary; 664 | } else if (xsre->selection == clipboard) { 665 | seltext = xsel.clipboard; 666 | } else { 667 | fprintf(stderr, 668 | "Unhandled clipboard selection 0x%lx\n", 669 | xsre->selection); 670 | return; 671 | } 672 | if (seltext != NULL) { 673 | XChangeProperty(xsre->display, xsre->requestor, 674 | xsre->property, xsre->target, 675 | 8, PropModeReplace, 676 | (uchar *)seltext, strlen(seltext)); 677 | xev.property = xsre->property; 678 | } 679 | } 680 | 681 | /* all done, send a notification to the listener */ 682 | if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) 683 | fprintf(stderr, "Error sending SelectionNotify event\n"); 684 | } 685 | 686 | void 687 | setsel(char *str, Time t) 688 | { 689 | if (!str) 690 | return; 691 | 692 | free(xsel.primary); 693 | xsel.primary = str; 694 | 695 | XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); 696 | if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) 697 | selclear(); 698 | } 699 | 700 | void 701 | xsetsel(char *str) 702 | { 703 | setsel(str, CurrentTime); 704 | } 705 | 706 | void 707 | brelease(XEvent *e) 708 | { 709 | int btn = e->xbutton.button; 710 | 711 | if (1 <= btn && btn <= 11) 712 | buttons &= ~(1 << (btn-1)); 713 | 714 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 715 | mousereport(e); 716 | return; 717 | } 718 | 719 | if (mouseaction(e, 1)) 720 | return; 721 | if (btn == Button1) 722 | mousesel(e, 1); 723 | } 724 | 725 | void 726 | bmotion(XEvent *e) 727 | { 728 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 729 | mousereport(e); 730 | return; 731 | } 732 | 733 | mousesel(e, 0); 734 | } 735 | 736 | void 737 | cresize(int width, int height) 738 | { 739 | int col, row; 740 | 741 | if (width != 0) 742 | win.w = width; 743 | if (height != 0) 744 | win.h = height; 745 | 746 | col = (win.w - 2 * borderpx) / win.cw; 747 | row = (win.h - 2 * borderpx) / win.ch; 748 | col = MAX(1, col); 749 | row = MAX(1, row); 750 | 751 | tresize(col, row); 752 | xresize(col, row); 753 | ttyresize(win.tw, win.th); 754 | } 755 | 756 | void 757 | xresize(int col, int row) 758 | { 759 | win.tw = col * win.cw; 760 | win.th = row * win.ch; 761 | 762 | XFreePixmap(xw.dpy, xw.buf); 763 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 764 | xw.depth); 765 | XftDrawChange(xw.draw, xw.buf); 766 | xclear(0, 0, win.w, win.h); 767 | 768 | /* resize to new width */ 769 | xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); 770 | } 771 | 772 | ushort 773 | sixd_to_16bit(int x) 774 | { 775 | return x == 0 ? 0 : 0x3737 + 0x2828 * x; 776 | } 777 | 778 | int 779 | xloadcolor(int i, const char *name, Color *ncolor) 780 | { 781 | XRenderColor color = { .alpha = 0xffff }; 782 | 783 | if (!name) { 784 | if (BETWEEN(i, 16, 255)) { /* 256 color */ 785 | if (i < 6*6*6+16) { /* same colors as xterm */ 786 | color.red = sixd_to_16bit( ((i-16)/36)%6 ); 787 | color.green = sixd_to_16bit( ((i-16)/6) %6 ); 788 | color.blue = sixd_to_16bit( ((i-16)/1) %6 ); 789 | } else { /* greyscale */ 790 | color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); 791 | color.green = color.blue = color.red; 792 | } 793 | return XftColorAllocValue(xw.dpy, xw.vis, 794 | xw.cmap, &color, ncolor); 795 | } else 796 | name = colorname[i]; 797 | } 798 | 799 | return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); 800 | } 801 | 802 | void 803 | xloadcols(void) 804 | { 805 | int i; 806 | static int loaded; 807 | Color *cp; 808 | 809 | if (loaded) { 810 | for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) 811 | XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); 812 | } else { 813 | dc.collen = MAX(LEN(colorname), 256); 814 | dc.col = xmalloc(dc.collen * sizeof(Color)); 815 | } 816 | 817 | for (i = 0; i < dc.collen; i++) 818 | if (!xloadcolor(i, NULL, &dc.col[i])) { 819 | if (colorname[i]) 820 | die("could not allocate color '%s'\n", colorname[i]); 821 | else 822 | die("could not allocate color %d\n", i); 823 | } 824 | 825 | /* set alpha value of bg color */ 826 | if (opt_alpha) 827 | alpha = strtof(opt_alpha, NULL); 828 | dc.bg_alpha = (unsigned short)(0xFFFF * alpha); 829 | dc.col[defaultbg].pixel &= 0x00FFFFFF; 830 | dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; 831 | loaded = 1; 832 | } 833 | 834 | int 835 | xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) 836 | { 837 | if (!BETWEEN(x, 0, dc.collen - 1)) 838 | return 1; 839 | 840 | *r = dc.col[x].color.red >> 8; 841 | *g = dc.col[x].color.green >> 8; 842 | *b = dc.col[x].color.blue >> 8; 843 | 844 | return 0; 845 | } 846 | 847 | int 848 | xsetcolorname(int x, const char *name) 849 | { 850 | Color ncolor; 851 | 852 | if (!BETWEEN(x, 0, dc.collen - 1)) 853 | return 1; 854 | 855 | if (!xloadcolor(x, name, &ncolor)) 856 | return 1; 857 | 858 | XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); 859 | dc.col[x] = ncolor; 860 | 861 | return 0; 862 | } 863 | 864 | /* 865 | * Absolute coordinates. 866 | */ 867 | void 868 | xclear(int x1, int y1, int x2, int y2) 869 | { 870 | XftDrawRect(xw.draw, 871 | &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], 872 | x1, y1, x2-x1, y2-y1); 873 | } 874 | 875 | void 876 | xhints(void) 877 | { 878 | XClassHint class = {opt_name ? opt_name : termname, 879 | opt_class ? opt_class : termname}; 880 | XWMHints wm = {.flags = InputHint, .input = 1}; 881 | XSizeHints *sizeh; 882 | 883 | sizeh = XAllocSizeHints(); 884 | 885 | sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; 886 | sizeh->height = win.h; 887 | sizeh->width = win.w; 888 | sizeh->height_inc = 1; 889 | sizeh->width_inc = 1; 890 | sizeh->base_height = 2 * borderpx; 891 | sizeh->base_width = 2 * borderpx; 892 | sizeh->min_height = win.ch + 2 * borderpx; 893 | sizeh->min_width = win.cw + 2 * borderpx; 894 | if (xw.isfixed) { 895 | sizeh->flags |= PMaxSize; 896 | sizeh->min_width = sizeh->max_width = win.w; 897 | sizeh->min_height = sizeh->max_height = win.h; 898 | } 899 | if (xw.gm & (XValue|YValue)) { 900 | sizeh->flags |= USPosition | PWinGravity; 901 | sizeh->x = xw.l; 902 | sizeh->y = xw.t; 903 | sizeh->win_gravity = xgeommasktogravity(xw.gm); 904 | } 905 | 906 | XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, 907 | &class); 908 | XFree(sizeh); 909 | } 910 | 911 | int 912 | xgeommasktogravity(int mask) 913 | { 914 | switch (mask & (XNegative|YNegative)) { 915 | case 0: 916 | return NorthWestGravity; 917 | case XNegative: 918 | return NorthEastGravity; 919 | case YNegative: 920 | return SouthWestGravity; 921 | } 922 | 923 | return SouthEastGravity; 924 | } 925 | 926 | int 927 | xloadfont(Font *f, FcPattern *pattern) 928 | { 929 | FcPattern *configured; 930 | FcPattern *match; 931 | FcResult result; 932 | XGlyphInfo extents; 933 | int wantattr, haveattr; 934 | 935 | /* 936 | * Manually configure instead of calling XftMatchFont 937 | * so that we can use the configured pattern for 938 | * "missing glyph" lookups. 939 | */ 940 | configured = FcPatternDuplicate(pattern); 941 | if (!configured) 942 | return 1; 943 | 944 | FcConfigSubstitute(NULL, configured, FcMatchPattern); 945 | XftDefaultSubstitute(xw.dpy, xw.scr, configured); 946 | 947 | match = FcFontMatch(NULL, configured, &result); 948 | if (!match) { 949 | FcPatternDestroy(configured); 950 | return 1; 951 | } 952 | 953 | if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { 954 | FcPatternDestroy(configured); 955 | FcPatternDestroy(match); 956 | return 1; 957 | } 958 | 959 | if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == 960 | XftResultMatch)) { 961 | /* 962 | * Check if xft was unable to find a font with the appropriate 963 | * slant but gave us one anyway. Try to mitigate. 964 | */ 965 | if ((XftPatternGetInteger(f->match->pattern, "slant", 0, 966 | &haveattr) != XftResultMatch) || haveattr < wantattr) { 967 | f->badslant = 1; 968 | fputs("font slant does not match\n", stderr); 969 | } 970 | } 971 | 972 | if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == 973 | XftResultMatch)) { 974 | if ((XftPatternGetInteger(f->match->pattern, "weight", 0, 975 | &haveattr) != XftResultMatch) || haveattr != wantattr) { 976 | f->badweight = 1; 977 | fputs("font weight does not match\n", stderr); 978 | } 979 | } 980 | 981 | XftTextExtentsUtf8(xw.dpy, f->match, 982 | (const FcChar8 *) ascii_printable, 983 | strlen(ascii_printable), &extents); 984 | 985 | f->set = NULL; 986 | f->pattern = configured; 987 | 988 | f->ascent = f->match->ascent; 989 | f->descent = f->match->descent; 990 | f->lbearing = 0; 991 | f->rbearing = f->match->max_advance_width; 992 | 993 | f->height = f->ascent + f->descent; 994 | f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); 995 | 996 | return 0; 997 | } 998 | 999 | void 1000 | xloadfonts(const char *fontstr, double fontsize) 1001 | { 1002 | FcPattern *pattern; 1003 | double fontval; 1004 | 1005 | if (fontstr[0] == '-') 1006 | pattern = XftXlfdParse(fontstr, False, False); 1007 | else 1008 | pattern = FcNameParse((const FcChar8 *)fontstr); 1009 | 1010 | if (!pattern) 1011 | die("can't open font %s\n", fontstr); 1012 | 1013 | if (fontsize > 1) { 1014 | FcPatternDel(pattern, FC_PIXEL_SIZE); 1015 | FcPatternDel(pattern, FC_SIZE); 1016 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); 1017 | usedfontsize = fontsize; 1018 | } else { 1019 | if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 1020 | FcResultMatch) { 1021 | usedfontsize = fontval; 1022 | } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == 1023 | FcResultMatch) { 1024 | usedfontsize = -1; 1025 | } else { 1026 | /* 1027 | * Default font size is 12, if none given. This is to 1028 | * have a known usedfontsize value. 1029 | */ 1030 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); 1031 | usedfontsize = 12; 1032 | } 1033 | defaultfontsize = usedfontsize; 1034 | } 1035 | 1036 | if (xloadfont(&dc.font, pattern)) 1037 | die("can't open font %s\n", fontstr); 1038 | 1039 | if (usedfontsize < 0) { 1040 | FcPatternGetDouble(dc.font.match->pattern, 1041 | FC_PIXEL_SIZE, 0, &fontval); 1042 | usedfontsize = fontval; 1043 | if (fontsize == 0) 1044 | defaultfontsize = fontval; 1045 | } 1046 | 1047 | /* Setting character width and height. */ 1048 | win.cw = ceilf(dc.font.width * cwscale); 1049 | win.ch = ceilf(dc.font.height * chscale); 1050 | 1051 | FcPatternDel(pattern, FC_SLANT); 1052 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 1053 | if (xloadfont(&dc.ifont, pattern)) 1054 | die("can't open font %s\n", fontstr); 1055 | 1056 | FcPatternDel(pattern, FC_WEIGHT); 1057 | FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 1058 | if (xloadfont(&dc.ibfont, pattern)) 1059 | die("can't open font %s\n", fontstr); 1060 | 1061 | FcPatternDel(pattern, FC_SLANT); 1062 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 1063 | if (xloadfont(&dc.bfont, pattern)) 1064 | die("can't open font %s\n", fontstr); 1065 | 1066 | FcPatternDestroy(pattern); 1067 | } 1068 | 1069 | void 1070 | xunloadfont(Font *f) 1071 | { 1072 | XftFontClose(xw.dpy, f->match); 1073 | FcPatternDestroy(f->pattern); 1074 | if (f->set) 1075 | FcFontSetDestroy(f->set); 1076 | } 1077 | 1078 | void 1079 | xunloadfonts(void) 1080 | { 1081 | /* Clear Harfbuzz font cache. */ 1082 | hbunloadfonts(); 1083 | 1084 | /* Free the loaded fonts in the font cache. */ 1085 | while (frclen > 0) 1086 | XftFontClose(xw.dpy, frc[--frclen].font); 1087 | 1088 | xunloadfont(&dc.font); 1089 | xunloadfont(&dc.bfont); 1090 | xunloadfont(&dc.ifont); 1091 | xunloadfont(&dc.ibfont); 1092 | } 1093 | 1094 | int 1095 | ximopen(Display *dpy) 1096 | { 1097 | XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; 1098 | XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; 1099 | 1100 | xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); 1101 | if (xw.ime.xim == NULL) 1102 | return 0; 1103 | 1104 | if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) 1105 | fprintf(stderr, "XSetIMValues: " 1106 | "Could not set XNDestroyCallback.\n"); 1107 | 1108 | xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, 1109 | NULL); 1110 | 1111 | if (xw.ime.xic == NULL) { 1112 | xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, 1113 | XIMPreeditNothing | XIMStatusNothing, 1114 | XNClientWindow, xw.win, 1115 | XNDestroyCallback, &icdestroy, 1116 | NULL); 1117 | } 1118 | if (xw.ime.xic == NULL) 1119 | fprintf(stderr, "XCreateIC: Could not create input context.\n"); 1120 | 1121 | return 1; 1122 | } 1123 | 1124 | void 1125 | ximinstantiate(Display *dpy, XPointer client, XPointer call) 1126 | { 1127 | if (ximopen(dpy)) 1128 | XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1129 | ximinstantiate, NULL); 1130 | } 1131 | 1132 | void 1133 | ximdestroy(XIM xim, XPointer client, XPointer call) 1134 | { 1135 | xw.ime.xim = NULL; 1136 | XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1137 | ximinstantiate, NULL); 1138 | XFree(xw.ime.spotlist); 1139 | } 1140 | 1141 | int 1142 | xicdestroy(XIC xim, XPointer client, XPointer call) 1143 | { 1144 | xw.ime.xic = NULL; 1145 | return 1; 1146 | } 1147 | 1148 | void 1149 | xinit(int cols, int rows) 1150 | { 1151 | XGCValues gcvalues; 1152 | Cursor cursor; 1153 | Window parent, root; 1154 | pid_t thispid = getpid(); 1155 | XColor xmousefg, xmousebg; 1156 | XWindowAttributes attr; 1157 | XVisualInfo vis; 1158 | 1159 | if (!(xw.dpy = XOpenDisplay(NULL))) 1160 | die("can't open display\n"); 1161 | xw.scr = XDefaultScreen(xw.dpy); 1162 | 1163 | if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { 1164 | parent = XRootWindow(xw.dpy, xw.scr); 1165 | xw.depth = 32; 1166 | } else { 1167 | XGetWindowAttributes(xw.dpy, parent, &attr); 1168 | xw.depth = attr.depth; 1169 | } 1170 | 1171 | XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); 1172 | xw.vis = vis.visual; 1173 | 1174 | /* font */ 1175 | if (!FcInit()) 1176 | die("could not init fontconfig.\n"); 1177 | 1178 | usedfont = (opt_font == NULL)? font : opt_font; 1179 | xloadfonts(usedfont, 0); 1180 | 1181 | /* Backup default alpha value */ 1182 | alpha_def = alpha; 1183 | 1184 | /* colors */ 1185 | xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); 1186 | xloadcols(); 1187 | 1188 | /* adjust fixed window geometry */ 1189 | win.w = 2 * borderpx + cols * win.cw; 1190 | win.h = 2 * borderpx + rows * win.ch; 1191 | if (xw.gm & XNegative) 1192 | xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; 1193 | if (xw.gm & YNegative) 1194 | xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; 1195 | 1196 | /* Events */ 1197 | xw.attrs.background_pixel = dc.col[defaultbg].pixel; 1198 | xw.attrs.border_pixel = dc.col[defaultbg].pixel; 1199 | xw.attrs.bit_gravity = NorthWestGravity; 1200 | xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask 1201 | | ExposureMask | VisibilityChangeMask | StructureNotifyMask 1202 | | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; 1203 | xw.attrs.colormap = xw.cmap; 1204 | 1205 | root = XRootWindow(xw.dpy, xw.scr); 1206 | xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, 1207 | win.w, win.h, 0, xw.depth, InputOutput, 1208 | xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity 1209 | | CWEventMask | CWColormap, &xw.attrs); 1210 | if (parent != root) 1211 | XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); 1212 | 1213 | memset(&gcvalues, 0, sizeof(gcvalues)); 1214 | gcvalues.graphics_exposures = False; 1215 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); 1216 | dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); 1217 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 1218 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); 1219 | 1220 | /* font spec buffer */ 1221 | xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); 1222 | 1223 | /* Xft rendering context */ 1224 | xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); 1225 | 1226 | /* input methods */ 1227 | if (!ximopen(xw.dpy)) { 1228 | XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1229 | ximinstantiate, NULL); 1230 | } 1231 | 1232 | /* white cursor, black outline */ 1233 | cursor = XCreateFontCursor(xw.dpy, mouseshape); 1234 | XDefineCursor(xw.dpy, xw.win, cursor); 1235 | 1236 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { 1237 | xmousefg.red = 0xffff; 1238 | xmousefg.green = 0xffff; 1239 | xmousefg.blue = 0xffff; 1240 | } 1241 | 1242 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { 1243 | xmousebg.red = 0x0000; 1244 | xmousebg.green = 0x0000; 1245 | xmousebg.blue = 0x0000; 1246 | } 1247 | 1248 | XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); 1249 | 1250 | xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); 1251 | xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); 1252 | xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); 1253 | xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); 1254 | XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); 1255 | 1256 | xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); 1257 | XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, 1258 | PropModeReplace, (uchar *)&thispid, 1); 1259 | 1260 | win.mode = MODE_NUMLOCK; 1261 | resettitle(); 1262 | xhints(); 1263 | XMapWindow(xw.dpy, xw.win); 1264 | XSync(xw.dpy, False); 1265 | 1266 | clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); 1267 | clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); 1268 | xsel.primary = NULL; 1269 | xsel.clipboard = NULL; 1270 | xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 1271 | if (xsel.xtarget == None) 1272 | xsel.xtarget = XA_STRING; 1273 | } 1274 | 1275 | void 1276 | xresetfontsettings(ushort mode, Font **font, int *frcflags) 1277 | { 1278 | *font = &dc.font; 1279 | if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { 1280 | *font = &dc.ibfont; 1281 | *frcflags = FRC_ITALICBOLD; 1282 | } else if (mode & ATTR_ITALIC) { 1283 | *font = &dc.ifont; 1284 | *frcflags = FRC_ITALIC; 1285 | } else if (mode & ATTR_BOLD) { 1286 | *font = &dc.bfont; 1287 | *frcflags = FRC_BOLD; 1288 | } 1289 | } 1290 | 1291 | int 1292 | xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) 1293 | { 1294 | float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; 1295 | ushort mode, prevmode = USHRT_MAX; 1296 | Font *font = &dc.font; 1297 | int frcflags = FRC_NORMAL; 1298 | float runewidth = win.cw; 1299 | Rune rune; 1300 | FT_UInt glyphidx; 1301 | FcResult fcres; 1302 | FcPattern *fcpattern, *fontpattern; 1303 | FcFontSet *fcsets[] = { NULL }; 1304 | FcCharSet *fccharset; 1305 | int i, f, length = 0, start = 0, numspecs = 0; 1306 | float cluster_xp = xp, cluster_yp = yp; 1307 | HbTransformData shaped = { 0 }; 1308 | 1309 | /* Initial values. */ 1310 | mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; 1311 | xresetfontsettings(mode, &font, &frcflags); 1312 | 1313 | for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { 1314 | mode = glyphs[i].mode & ~ATTR_WRAP; 1315 | 1316 | /* Skip dummy wide-character spacing. */ 1317 | if (mode & ATTR_WDUMMY && i < (len - 1)) 1318 | continue; 1319 | 1320 | if ( 1321 | prevmode != mode 1322 | || ATTRCMP(glyphs[start], glyphs[i]) 1323 | || selected(x + i, y) != selected(x + start, y) 1324 | || i == (len - 1) 1325 | ) { 1326 | /* Handle 1-character wide segments and end of line */ 1327 | length = i - start; 1328 | if (i == start) { 1329 | length = 1; 1330 | } else if (i == (len - 1)) { 1331 | length = (i - start + 1); 1332 | } 1333 | 1334 | /* Shape the segment. */ 1335 | hbtransform(&shaped, font->match, glyphs, start, length); 1336 | runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f); 1337 | cluster_xp = xp; cluster_yp = yp; 1338 | for (int code_idx = 0; code_idx < shaped.count; code_idx++) { 1339 | int idx = shaped.glyphs[code_idx].cluster; 1340 | 1341 | if (glyphs[start + idx].mode & ATTR_WDUMMY) 1342 | continue; 1343 | 1344 | /* Advance the drawing cursor if we've moved to a new cluster */ 1345 | if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { 1346 | xp += runewidth; 1347 | cluster_xp = xp; 1348 | cluster_yp = yp; 1349 | runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); 1350 | } 1351 | 1352 | if (shaped.glyphs[code_idx].codepoint != 0) { 1353 | /* If symbol is found, put it into the specs. */ 1354 | specs[numspecs].font = font->match; 1355 | specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; 1356 | specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); 1357 | specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); 1358 | cluster_xp += shaped.positions[code_idx].x_advance / 64.; 1359 | cluster_yp += shaped.positions[code_idx].y_advance / 64.; 1360 | numspecs++; 1361 | } else { 1362 | /* If it's not found, try to fetch it through the font cache. */ 1363 | rune = glyphs[start + idx].u; 1364 | for (f = 0; f < frclen; f++) { 1365 | glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); 1366 | /* Everything correct. */ 1367 | if (glyphidx && frc[f].flags == frcflags) 1368 | break; 1369 | /* We got a default font for a not found glyph. */ 1370 | if (!glyphidx && frc[f].flags == frcflags 1371 | && frc[f].unicodep == rune) { 1372 | break; 1373 | } 1374 | } 1375 | 1376 | /* Nothing was found. Use fontconfig to find matching font. */ 1377 | if (f >= frclen) { 1378 | if (!font->set) 1379 | font->set = FcFontSort(0, font->pattern, 1380 | 1, 0, &fcres); 1381 | fcsets[0] = font->set; 1382 | 1383 | /* 1384 | * Nothing was found in the cache. Now use 1385 | * some dozen of Fontconfig calls to get the 1386 | * font for one single character. 1387 | * 1388 | * Xft and fontconfig are design failures. 1389 | */ 1390 | fcpattern = FcPatternDuplicate(font->pattern); 1391 | fccharset = FcCharSetCreate(); 1392 | 1393 | FcCharSetAddChar(fccharset, rune); 1394 | FcPatternAddCharSet(fcpattern, FC_CHARSET, 1395 | fccharset); 1396 | FcPatternAddBool(fcpattern, FC_SCALABLE, 1); 1397 | 1398 | FcConfigSubstitute(0, fcpattern, 1399 | FcMatchPattern); 1400 | FcDefaultSubstitute(fcpattern); 1401 | 1402 | fontpattern = FcFontSetMatch(0, fcsets, 1, 1403 | fcpattern, &fcres); 1404 | 1405 | /* Allocate memory for the new cache entry. */ 1406 | if (frclen >= frccap) { 1407 | frccap += 16; 1408 | frc = xrealloc(frc, frccap * sizeof(Fontcache)); 1409 | } 1410 | 1411 | frc[frclen].font = XftFontOpenPattern(xw.dpy, 1412 | fontpattern); 1413 | if (!frc[frclen].font) 1414 | die("XftFontOpenPattern failed seeking fallback font: %s\n", 1415 | strerror(errno)); 1416 | frc[frclen].flags = frcflags; 1417 | frc[frclen].unicodep = rune; 1418 | 1419 | glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); 1420 | 1421 | f = frclen; 1422 | frclen++; 1423 | 1424 | FcPatternDestroy(fcpattern); 1425 | FcCharSetDestroy(fccharset); 1426 | } 1427 | 1428 | specs[numspecs].font = frc[f].font; 1429 | specs[numspecs].glyph = glyphidx; 1430 | specs[numspecs].x = (short)xp; 1431 | specs[numspecs].y = (short)yp; 1432 | numspecs++; 1433 | } 1434 | } 1435 | 1436 | /* Cleanup and get ready for next segment. */ 1437 | hbcleanup(&shaped); 1438 | start = i; 1439 | 1440 | /* Determine font for glyph if different from previous glyph. */ 1441 | if (prevmode != mode) { 1442 | prevmode = mode; 1443 | xresetfontsettings(mode, &font, &frcflags); 1444 | yp = winy + font->ascent; 1445 | } 1446 | } 1447 | } 1448 | 1449 | return numspecs; 1450 | } 1451 | 1452 | void 1453 | chgalpha(const Arg *arg) 1454 | { 1455 | if (arg->f == -1.0f && alpha >= 0.1f) 1456 | alpha -= 0.1f; 1457 | else if (arg->f == 1.0f && alpha < 1.0f) 1458 | alpha += 0.1f; 1459 | else if (arg->f == 0.0f) 1460 | alpha = alpha_def; 1461 | else 1462 | return; 1463 | 1464 | dc.bg_alpha = (unsigned short)(0xFFFF * alpha); 1465 | /* Required to remove artifacting from borderpx */ 1466 | cresize(0, 0); 1467 | redraw(); 1468 | } 1469 | 1470 | void 1471 | xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) 1472 | { 1473 | int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, 1474 | width = charlen * win.cw; 1475 | Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; 1476 | XRenderColor colfg, colbg; 1477 | XRectangle r; 1478 | 1479 | /* Fallback on color display for attributes not supported by the font */ 1480 | if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { 1481 | if (dc.ibfont.badslant || dc.ibfont.badweight) 1482 | base.fg = defaultattr; 1483 | } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || 1484 | (base.mode & ATTR_BOLD && dc.bfont.badweight)) { 1485 | base.fg = defaultattr; 1486 | } 1487 | 1488 | if (IS_TRUECOL(base.fg)) { 1489 | colfg.alpha = 0xffff; 1490 | colfg.red = TRUERED(base.fg); 1491 | colfg.green = TRUEGREEN(base.fg); 1492 | colfg.blue = TRUEBLUE(base.fg); 1493 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); 1494 | fg = &truefg; 1495 | } else { 1496 | fg = &dc.col[base.fg]; 1497 | } 1498 | 1499 | if (IS_TRUECOL(base.bg)) { 1500 | colbg.alpha = dc.bg_alpha; 1501 | colbg.green = TRUEGREEN(base.bg); 1502 | colbg.red = TRUERED(base.bg); 1503 | colbg.blue = TRUEBLUE(base.bg); 1504 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); 1505 | } else { 1506 | truebg = dc.col[base.bg]; 1507 | truebg.color.alpha = dc.bg_alpha; 1508 | } 1509 | bg = &truebg; 1510 | 1511 | /* Change basic system colors [0-7] to bright system colors [8-15] */ 1512 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) 1513 | fg = &dc.col[base.fg + 8]; 1514 | 1515 | if (IS_SET(MODE_REVERSE)) { 1516 | if (fg == &dc.col[defaultfg]) { 1517 | fg = &dc.col[defaultbg]; 1518 | } else { 1519 | colfg.red = ~fg->color.red; 1520 | colfg.green = ~fg->color.green; 1521 | colfg.blue = ~fg->color.blue; 1522 | colfg.alpha = fg->color.alpha; 1523 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, 1524 | &revfg); 1525 | fg = &revfg; 1526 | } 1527 | 1528 | if (bg == &dc.col[defaultbg]) { 1529 | bg = &dc.col[defaultfg]; 1530 | } else { 1531 | colbg.red = ~bg->color.red; 1532 | colbg.green = ~bg->color.green; 1533 | colbg.blue = ~bg->color.blue; 1534 | colbg.alpha = bg->color.alpha; 1535 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, 1536 | &revbg); 1537 | bg = &revbg; 1538 | } 1539 | } 1540 | 1541 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { 1542 | colfg.red = fg->color.red / 2; 1543 | colfg.green = fg->color.green / 2; 1544 | colfg.blue = fg->color.blue / 2; 1545 | colfg.alpha = fg->color.alpha; 1546 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); 1547 | fg = &revfg; 1548 | } 1549 | 1550 | if (base.mode & ATTR_REVERSE) { 1551 | temp = fg; 1552 | fg = bg; 1553 | bg = temp; 1554 | } 1555 | 1556 | if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) 1557 | fg = bg; 1558 | 1559 | if (base.mode & ATTR_INVISIBLE) 1560 | fg = bg; 1561 | 1562 | /* Intelligent cleaning up of the borders. */ 1563 | if (x == 0) { 1564 | xclear(0, (y == 0)? 0 : winy, borderpx, 1565 | winy + win.ch + 1566 | ((winy + win.ch >= borderpx + win.th)? win.h : 0)); 1567 | } 1568 | if (winx + width >= borderpx + win.tw) { 1569 | xclear(winx + width, (y == 0)? 0 : winy, win.w, 1570 | ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); 1571 | } 1572 | if (y == 0) 1573 | xclear(winx, 0, winx + width, borderpx); 1574 | if (winy + win.ch >= borderpx + win.th) 1575 | xclear(winx, winy + win.ch, winx + width, win.h); 1576 | 1577 | /* Clean up the region we want to draw to. */ 1578 | XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); 1579 | 1580 | /* Set the clip region because Xft is sometimes dirty. */ 1581 | r.x = 0; 1582 | r.y = 0; 1583 | r.height = win.ch; 1584 | r.width = width; 1585 | XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); 1586 | 1587 | /* Render the glyphs. */ 1588 | XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 1589 | 1590 | /* Render underline and strikethrough. */ 1591 | if (base.mode & ATTR_UNDERLINE) { 1592 | XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, 1593 | width, 1); 1594 | } 1595 | 1596 | if (base.mode & ATTR_STRUCK) { 1597 | XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, 1598 | width, 1); 1599 | } 1600 | 1601 | /* Reset clip to none. */ 1602 | XftDrawSetClip(xw.draw, 0); 1603 | } 1604 | 1605 | void 1606 | xdrawglyph(Glyph g, int x, int y) 1607 | { 1608 | int numspecs; 1609 | XftGlyphFontSpec *specs = xw.specbuf; 1610 | 1611 | numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); 1612 | xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); 1613 | } 1614 | 1615 | void 1616 | xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) 1617 | { 1618 | Color drawcol; 1619 | 1620 | /* remove the old cursor */ 1621 | if (selected(ox, oy)) 1622 | og.mode ^= ATTR_REVERSE; 1623 | 1624 | /* Redraw the line where cursor was previously. 1625 | * It will restore the ligatures broken by the cursor. */ 1626 | xdrawline(line, 0, oy, len); 1627 | 1628 | if (IS_SET(MODE_HIDE)) 1629 | return; 1630 | 1631 | /* 1632 | * Select the right color for the right mode. 1633 | */ 1634 | g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; 1635 | 1636 | if (IS_SET(MODE_REVERSE)) { 1637 | g.mode |= ATTR_REVERSE; 1638 | g.bg = defaultfg; 1639 | if (selected(cx, cy)) { 1640 | drawcol = dc.col[defaultcs]; 1641 | g.fg = defaultrcs; 1642 | } else { 1643 | drawcol = dc.col[defaultrcs]; 1644 | g.fg = defaultcs; 1645 | } 1646 | } else { 1647 | if (selected(cx, cy)) { 1648 | g.fg = defaultfg; 1649 | g.bg = defaultrcs; 1650 | } else { 1651 | g.fg = defaultbg; 1652 | g.bg = defaultcs; 1653 | } 1654 | drawcol = dc.col[g.bg]; 1655 | } 1656 | 1657 | /* draw the new one */ 1658 | if (IS_SET(MODE_FOCUSED)) { 1659 | switch (win.cursor) { 1660 | case 7: /* st extension */ 1661 | g.u = 0x2603; /* snowman (U+2603) */ 1662 | /* FALLTHROUGH */ 1663 | case 0: /* Blinking Block */ 1664 | case 1: /* Blinking Block (Default) */ 1665 | case 2: /* Steady Block */ 1666 | xdrawglyph(g, cx, cy); 1667 | break; 1668 | case 3: /* Blinking Underline */ 1669 | case 4: /* Steady Underline */ 1670 | XftDrawRect(xw.draw, &drawcol, 1671 | borderpx + cx * win.cw, 1672 | borderpx + (cy + 1) * win.ch - \ 1673 | cursorthickness, 1674 | win.cw, cursorthickness); 1675 | break; 1676 | case 5: /* Blinking bar */ 1677 | case 6: /* Steady bar */ 1678 | XftDrawRect(xw.draw, &drawcol, 1679 | borderpx + cx * win.cw, 1680 | borderpx + cy * win.ch, 1681 | cursorthickness, win.ch); 1682 | break; 1683 | } 1684 | } else { 1685 | XftDrawRect(xw.draw, &drawcol, 1686 | borderpx + cx * win.cw, 1687 | borderpx + cy * win.ch, 1688 | win.cw - 1, 1); 1689 | XftDrawRect(xw.draw, &drawcol, 1690 | borderpx + cx * win.cw, 1691 | borderpx + cy * win.ch, 1692 | 1, win.ch - 1); 1693 | XftDrawRect(xw.draw, &drawcol, 1694 | borderpx + (cx + 1) * win.cw - 1, 1695 | borderpx + cy * win.ch, 1696 | 1, win.ch - 1); 1697 | XftDrawRect(xw.draw, &drawcol, 1698 | borderpx + cx * win.cw, 1699 | borderpx + (cy + 1) * win.ch - 1, 1700 | win.cw, 1); 1701 | } 1702 | } 1703 | 1704 | void 1705 | xsetenv(void) 1706 | { 1707 | char buf[sizeof(long) * 8 + 1]; 1708 | 1709 | snprintf(buf, sizeof(buf), "%lu", xw.win); 1710 | setenv("WINDOWID", buf, 1); 1711 | } 1712 | 1713 | void 1714 | xseticontitle(char *p) 1715 | { 1716 | XTextProperty prop; 1717 | DEFAULT(p, opt_title); 1718 | 1719 | if (p[0] == '\0') 1720 | p = opt_title; 1721 | 1722 | if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 1723 | &prop) != Success) 1724 | return; 1725 | XSetWMIconName(xw.dpy, xw.win, &prop); 1726 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); 1727 | XFree(prop.value); 1728 | } 1729 | 1730 | void 1731 | xsettitle(char *p) 1732 | { 1733 | XTextProperty prop; 1734 | DEFAULT(p, opt_title); 1735 | 1736 | if (p[0] == '\0') 1737 | p = opt_title; 1738 | 1739 | if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 1740 | &prop) != Success) 1741 | return; 1742 | XSetWMName(xw.dpy, xw.win, &prop); 1743 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); 1744 | XFree(prop.value); 1745 | } 1746 | 1747 | int 1748 | xstartdraw(void) 1749 | { 1750 | return IS_SET(MODE_VISIBLE); 1751 | } 1752 | 1753 | void 1754 | xdrawline(Line line, int x1, int y1, int x2) 1755 | { 1756 | int i, x, ox, numspecs; 1757 | Glyph base, new; 1758 | XftGlyphFontSpec *specs = xw.specbuf; 1759 | 1760 | i = ox = 0; 1761 | for (x = x1; x < x2; x++) { 1762 | new = line[x]; 1763 | if (new.mode == ATTR_WDUMMY) 1764 | continue; 1765 | if (selected(x, y1)) 1766 | new.mode ^= ATTR_REVERSE; 1767 | if ((i > 0) && ATTRCMP(base, new)) { 1768 | numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); 1769 | xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); 1770 | i = 0; 1771 | } 1772 | if (i == 0) { 1773 | ox = x; 1774 | base = new; 1775 | } 1776 | i++; 1777 | } 1778 | if (i > 0) { 1779 | numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); 1780 | xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); 1781 | } 1782 | } 1783 | 1784 | void 1785 | xfinishdraw(void) 1786 | { 1787 | XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, 1788 | win.h, 0, 0); 1789 | XSetForeground(xw.dpy, dc.gc, 1790 | dc.col[IS_SET(MODE_REVERSE)? 1791 | defaultfg : defaultbg].pixel); 1792 | } 1793 | 1794 | void 1795 | xximspot(int x, int y) 1796 | { 1797 | if (xw.ime.xic == NULL) 1798 | return; 1799 | 1800 | xw.ime.spot.x = borderpx + x * win.cw; 1801 | xw.ime.spot.y = borderpx + (y + 1) * win.ch; 1802 | 1803 | XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); 1804 | } 1805 | 1806 | void 1807 | expose(XEvent *ev) 1808 | { 1809 | redraw(); 1810 | } 1811 | 1812 | void 1813 | visibility(XEvent *ev) 1814 | { 1815 | XVisibilityEvent *e = &ev->xvisibility; 1816 | 1817 | MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); 1818 | } 1819 | 1820 | void 1821 | unmap(XEvent *ev) 1822 | { 1823 | win.mode &= ~MODE_VISIBLE; 1824 | } 1825 | 1826 | void 1827 | xsetpointermotion(int set) 1828 | { 1829 | MODBIT(xw.attrs.event_mask, set, PointerMotionMask); 1830 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); 1831 | } 1832 | 1833 | void 1834 | xsetmode(int set, unsigned int flags) 1835 | { 1836 | int mode = win.mode; 1837 | MODBIT(win.mode, set, flags); 1838 | if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) 1839 | redraw(); 1840 | } 1841 | 1842 | int 1843 | xsetcursor(int cursor) 1844 | { 1845 | if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ 1846 | return 1; 1847 | win.cursor = cursor; 1848 | return 0; 1849 | } 1850 | 1851 | void 1852 | xseturgency(int add) 1853 | { 1854 | XWMHints *h = XGetWMHints(xw.dpy, xw.win); 1855 | 1856 | MODBIT(h->flags, add, XUrgencyHint); 1857 | XSetWMHints(xw.dpy, xw.win, h); 1858 | XFree(h); 1859 | } 1860 | 1861 | void 1862 | xbell(void) 1863 | { 1864 | if (!(IS_SET(MODE_FOCUSED))) 1865 | xseturgency(1); 1866 | if (bellvolume) 1867 | XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); 1868 | } 1869 | 1870 | void 1871 | focus(XEvent *ev) 1872 | { 1873 | XFocusChangeEvent *e = &ev->xfocus; 1874 | 1875 | if (e->mode == NotifyGrab) 1876 | return; 1877 | 1878 | if (ev->type == FocusIn) { 1879 | if (xw.ime.xic) 1880 | XSetICFocus(xw.ime.xic); 1881 | win.mode |= MODE_FOCUSED; 1882 | xseturgency(0); 1883 | if (IS_SET(MODE_FOCUS)) 1884 | ttywrite("\033[I", 3, 0); 1885 | } else { 1886 | if (xw.ime.xic) 1887 | XUnsetICFocus(xw.ime.xic); 1888 | win.mode &= ~MODE_FOCUSED; 1889 | if (IS_SET(MODE_FOCUS)) 1890 | ttywrite("\033[O", 3, 0); 1891 | } 1892 | } 1893 | 1894 | int 1895 | match(uint mask, uint state) 1896 | { 1897 | return mask == XK_ANY_MOD || mask == (state & ~ignoremod); 1898 | } 1899 | 1900 | char* 1901 | kmap(KeySym k, uint state) 1902 | { 1903 | Key *kp; 1904 | int i; 1905 | 1906 | /* Check for mapped keys out of X11 function keys. */ 1907 | for (i = 0; i < LEN(mappedkeys); i++) { 1908 | if (mappedkeys[i] == k) 1909 | break; 1910 | } 1911 | if (i == LEN(mappedkeys)) { 1912 | if ((k & 0xFFFF) < 0xFD00) 1913 | return NULL; 1914 | } 1915 | 1916 | for (kp = key; kp < key + LEN(key); kp++) { 1917 | if (kp->k != k) 1918 | continue; 1919 | 1920 | if (!match(kp->mask, state)) 1921 | continue; 1922 | 1923 | if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) 1924 | continue; 1925 | if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) 1926 | continue; 1927 | 1928 | if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) 1929 | continue; 1930 | 1931 | return kp->s; 1932 | } 1933 | 1934 | return NULL; 1935 | } 1936 | 1937 | void 1938 | kpress(XEvent *ev) 1939 | { 1940 | XKeyEvent *e = &ev->xkey; 1941 | KeySym ksym = NoSymbol; 1942 | char buf[64], *customkey; 1943 | int len; 1944 | Rune c; 1945 | Status status; 1946 | Shortcut *bp; 1947 | 1948 | if (IS_SET(MODE_KBDLOCK)) 1949 | return; 1950 | 1951 | if (xw.ime.xic) { 1952 | len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); 1953 | if (status == XBufferOverflow) 1954 | return; 1955 | } else { 1956 | len = XLookupString(e, buf, sizeof buf, &ksym, NULL); 1957 | } 1958 | /* 1. shortcuts */ 1959 | for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 1960 | if (ksym == bp->keysym && match(bp->mod, e->state)) { 1961 | bp->func(&(bp->arg)); 1962 | return; 1963 | } 1964 | } 1965 | 1966 | /* 2. custom keys from config.h */ 1967 | if ((customkey = kmap(ksym, e->state))) { 1968 | ttywrite(customkey, strlen(customkey), 1); 1969 | return; 1970 | } 1971 | 1972 | /* 3. composed string from input method */ 1973 | if (len == 0) 1974 | return; 1975 | if (len == 1 && e->state & Mod1Mask) { 1976 | if (IS_SET(MODE_8BIT)) { 1977 | if (*buf < 0177) { 1978 | c = *buf | 0x80; 1979 | len = utf8encode(c, buf); 1980 | } 1981 | } else { 1982 | buf[1] = buf[0]; 1983 | buf[0] = '\033'; 1984 | len = 2; 1985 | } 1986 | } 1987 | ttywrite(buf, len, 1); 1988 | } 1989 | 1990 | void 1991 | cmessage(XEvent *e) 1992 | { 1993 | /* 1994 | * See xembed specs 1995 | * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html 1996 | */ 1997 | if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { 1998 | if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { 1999 | win.mode |= MODE_FOCUSED; 2000 | xseturgency(0); 2001 | } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { 2002 | win.mode &= ~MODE_FOCUSED; 2003 | } 2004 | } else if (e->xclient.data.l[0] == xw.wmdeletewin) { 2005 | ttyhangup(); 2006 | exit(0); 2007 | } 2008 | } 2009 | 2010 | void 2011 | resize(XEvent *e) 2012 | { 2013 | if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) 2014 | return; 2015 | 2016 | cresize(e->xconfigure.width, e->xconfigure.height); 2017 | } 2018 | 2019 | void 2020 | run(void) 2021 | { 2022 | XEvent ev; 2023 | int w = win.w, h = win.h; 2024 | fd_set rfd; 2025 | int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; 2026 | struct timespec seltv, *tv, now, lastblink, trigger; 2027 | double timeout; 2028 | 2029 | /* Waiting for window mapping */ 2030 | do { 2031 | XNextEvent(xw.dpy, &ev); 2032 | /* 2033 | * This XFilterEvent call is required because of XOpenIM. It 2034 | * does filter out the key event and some client message for 2035 | * the input method too. 2036 | */ 2037 | if (XFilterEvent(&ev, None)) 2038 | continue; 2039 | if (ev.type == ConfigureNotify) { 2040 | w = ev.xconfigure.width; 2041 | h = ev.xconfigure.height; 2042 | } 2043 | } while (ev.type != MapNotify); 2044 | 2045 | ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); 2046 | cresize(w, h); 2047 | 2048 | for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { 2049 | FD_ZERO(&rfd); 2050 | FD_SET(ttyfd, &rfd); 2051 | FD_SET(xfd, &rfd); 2052 | 2053 | if (XPending(xw.dpy)) 2054 | timeout = 0; /* existing events might not set xfd */ 2055 | 2056 | seltv.tv_sec = timeout / 1E3; 2057 | seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); 2058 | tv = timeout >= 0 ? &seltv : NULL; 2059 | 2060 | if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { 2061 | if (errno == EINTR) 2062 | continue; 2063 | die("select failed: %s\n", strerror(errno)); 2064 | } 2065 | clock_gettime(CLOCK_MONOTONIC, &now); 2066 | 2067 | if (FD_ISSET(ttyfd, &rfd)) 2068 | ttyread(); 2069 | 2070 | xev = 0; 2071 | while (XPending(xw.dpy)) { 2072 | xev = 1; 2073 | XNextEvent(xw.dpy, &ev); 2074 | if (XFilterEvent(&ev, None)) 2075 | continue; 2076 | if (handler[ev.type]) 2077 | (handler[ev.type])(&ev); 2078 | } 2079 | 2080 | /* 2081 | * To reduce flicker and tearing, when new content or event 2082 | * triggers drawing, we first wait a bit to ensure we got 2083 | * everything, and if nothing new arrives - we draw. 2084 | * We start with trying to wait minlatency ms. If more content 2085 | * arrives sooner, we retry with shorter and shorter periods, 2086 | * and eventually draw even without idle after maxlatency ms. 2087 | * Typically this results in low latency while interacting, 2088 | * maximum latency intervals during `cat huge.txt`, and perfect 2089 | * sync with periodic updates from animations/key-repeats/etc. 2090 | */ 2091 | if (FD_ISSET(ttyfd, &rfd) || xev) { 2092 | if (!drawing) { 2093 | trigger = now; 2094 | drawing = 1; 2095 | } 2096 | timeout = (maxlatency - TIMEDIFF(now, trigger)) \ 2097 | / maxlatency * minlatency; 2098 | if (timeout > 0) 2099 | continue; /* we have time, try to find idle */ 2100 | } 2101 | 2102 | /* idle detected or maxlatency exhausted -> draw */ 2103 | timeout = -1; 2104 | if (blinktimeout && tattrset(ATTR_BLINK)) { 2105 | timeout = blinktimeout - TIMEDIFF(now, lastblink); 2106 | if (timeout <= 0) { 2107 | if (-timeout > blinktimeout) /* start visible */ 2108 | win.mode |= MODE_BLINK; 2109 | win.mode ^= MODE_BLINK; 2110 | tsetdirtattr(ATTR_BLINK); 2111 | lastblink = now; 2112 | timeout = blinktimeout; 2113 | } 2114 | } 2115 | 2116 | draw(); 2117 | XFlush(xw.dpy); 2118 | drawing = 0; 2119 | } 2120 | } 2121 | 2122 | 2123 | #define XRESOURCE_LOAD_META(NAME) \ 2124 | if(!XrmGetResource(xrdb, "st." NAME, "st." NAME, &type, &ret)) \ 2125 | XrmGetResource(xrdb, "*." NAME, "*." NAME, &type, &ret); \ 2126 | if (ret.addr != NULL && !strncmp("String", type, 64)) 2127 | 2128 | #define XRESOURCE_LOAD_STRING(NAME, DST) \ 2129 | XRESOURCE_LOAD_META(NAME) \ 2130 | DST = ret.addr; 2131 | 2132 | #define XRESOURCE_LOAD_CHAR(NAME, DST) \ 2133 | XRESOURCE_LOAD_META(NAME) \ 2134 | DST = ret.addr[0]; 2135 | 2136 | #define XRESOURCE_LOAD_INTEGER(NAME, DST) \ 2137 | XRESOURCE_LOAD_META(NAME) \ 2138 | DST = strtoul(ret.addr, NULL, 10); 2139 | 2140 | #define XRESOURCE_LOAD_FLOAT(NAME, DST) \ 2141 | XRESOURCE_LOAD_META(NAME) \ 2142 | DST = strtof(ret.addr, NULL); 2143 | 2144 | void 2145 | xrdb_load(void) 2146 | { 2147 | /* XXX */ 2148 | char *xrm; 2149 | char *type; 2150 | XrmDatabase xrdb; 2151 | XrmValue ret; 2152 | Display *dpy; 2153 | 2154 | if(!(dpy = XOpenDisplay(NULL))) 2155 | die("Can't open display\n"); 2156 | 2157 | XrmInitialize(); 2158 | xrm = XResourceManagerString(dpy); 2159 | 2160 | if (xrm != NULL) { 2161 | xrdb = XrmGetStringDatabase(xrm); 2162 | 2163 | /* handling colors here without macros to do via loop. */ 2164 | int i = 0; 2165 | char loadValue[12] = ""; 2166 | for (i = 0; i < 256; i++) 2167 | { 2168 | sprintf(loadValue, "%s%d", "st.color", i); 2169 | 2170 | if(!XrmGetResource(xrdb, loadValue, loadValue, &type, &ret)) 2171 | { 2172 | sprintf(loadValue, "%s%d", "*.color", i); 2173 | if (!XrmGetResource(xrdb, loadValue, loadValue, &type, &ret)) 2174 | /* reset if not found (unless in range for defaults). */ 2175 | if (i > 15) 2176 | colorname[i] = NULL; 2177 | } 2178 | 2179 | if (ret.addr != NULL && !strncmp("String", type, 64)) 2180 | colorname[i] = ret.addr; 2181 | } 2182 | 2183 | XRESOURCE_LOAD_STRING("foreground", colorname[defaultfg]); 2184 | XRESOURCE_LOAD_STRING("background", colorname[defaultbg]); 2185 | XRESOURCE_LOAD_STRING("cursorColor", colorname[defaultcs]) 2186 | else { 2187 | // this looks confusing because we are chaining off of the if 2188 | // in the macro. probably we should be wrapping everything blocks 2189 | // so this isn't possible... 2190 | defaultcs = defaultfg; 2191 | } 2192 | XRESOURCE_LOAD_STRING("reverse-cursor", colorname[defaultrcs]) 2193 | else { 2194 | // see above. 2195 | defaultrcs = defaultbg; 2196 | } 2197 | 2198 | XRESOURCE_LOAD_STRING("font", font); 2199 | XRESOURCE_LOAD_STRING("termname", termname); 2200 | 2201 | XRESOURCE_LOAD_INTEGER("blinktimeout", blinktimeout); 2202 | XRESOURCE_LOAD_INTEGER("bellvolume", bellvolume); 2203 | XRESOURCE_LOAD_INTEGER("borderpx", borderpx); 2204 | XRESOURCE_LOAD_INTEGER("cursorshape", cursorshape); 2205 | 2206 | XRESOURCE_LOAD_FLOAT("cwscale", cwscale); 2207 | XRESOURCE_LOAD_FLOAT("chscale", chscale); 2208 | } 2209 | XFlush(dpy); 2210 | } 2211 | 2212 | void 2213 | reload(int sig) 2214 | { 2215 | xrdb_load(); 2216 | 2217 | /* colors, fonts */ 2218 | xloadcols(); 2219 | xunloadfonts(); 2220 | xloadfonts(font, 0); 2221 | 2222 | /* pretend the window just got resized */ 2223 | cresize(win.w, win.h); 2224 | 2225 | redraw(); 2226 | 2227 | /* triggers re-render if we're visible. */ 2228 | ttywrite("\033[O", 3, 1); 2229 | 2230 | signal(SIGUSR1, reload); 2231 | } 2232 | 2233 | 2234 | void 2235 | usage(void) 2236 | { 2237 | die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" 2238 | " [-n name] [-o file]\n" 2239 | " [-T title] [-t title] [-w windowid]" 2240 | " [[-e] command [args ...]]\n" 2241 | " %s [-aiv] [-c class] [-f font] [-g geometry]" 2242 | " [-n name] [-o file]\n" 2243 | " [-T title] [-t title] [-w windowid] -l line" 2244 | " [stty_args ...]\n", argv0, argv0); 2245 | } 2246 | 2247 | int 2248 | main(int argc, char *argv[]) 2249 | { 2250 | xw.l = xw.t = 0; 2251 | xw.isfixed = False; 2252 | xsetcursor(cursorshape); 2253 | 2254 | ARGBEGIN { 2255 | case 'a': 2256 | allowaltscreen = 0; 2257 | break; 2258 | case 'A': 2259 | opt_alpha = EARGF(usage()); 2260 | break; 2261 | case 'c': 2262 | opt_class = EARGF(usage()); 2263 | break; 2264 | case 'e': 2265 | if (argc > 0) 2266 | --argc, ++argv; 2267 | goto run; 2268 | case 'f': 2269 | opt_font = EARGF(usage()); 2270 | break; 2271 | case 'g': 2272 | xw.gm = XParseGeometry(EARGF(usage()), 2273 | &xw.l, &xw.t, &cols, &rows); 2274 | break; 2275 | case 'i': 2276 | xw.isfixed = 1; 2277 | break; 2278 | case 'o': 2279 | opt_io = EARGF(usage()); 2280 | break; 2281 | case 'l': 2282 | opt_line = EARGF(usage()); 2283 | break; 2284 | case 'n': 2285 | opt_name = EARGF(usage()); 2286 | break; 2287 | case 't': 2288 | case 'T': 2289 | opt_title = EARGF(usage()); 2290 | break; 2291 | case 'w': 2292 | opt_embed = EARGF(usage()); 2293 | break; 2294 | case 'v': 2295 | die("%s " VERSION "\n", argv0); 2296 | break; 2297 | default: 2298 | usage(); 2299 | } ARGEND; 2300 | 2301 | run: 2302 | if (argc > 0) /* eat all remaining arguments */ 2303 | opt_cmd = argv; 2304 | 2305 | if (!opt_title) 2306 | opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; 2307 | 2308 | setlocale(LC_CTYPE, ""); 2309 | XSetLocaleModifiers(""); 2310 | xrdb_load(); 2311 | signal(SIGUSR1, reload); 2312 | cols = MAX(cols, 1); 2313 | rows = MAX(rows, 1); 2314 | tnew(cols, rows); 2315 | xinit(cols, rows); 2316 | xsetenv(); 2317 | selinit(); 2318 | run(); 2319 | 2320 | return 0; 2321 | } 2322 | -------------------------------------------------------------------------------- /st.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "st.h" 21 | #include "win.h" 22 | 23 | #if defined(__linux) 24 | #include 25 | #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 26 | #include 27 | #elif defined(__FreeBSD__) || defined(__DragonFly__) 28 | #include 29 | #endif 30 | 31 | /* Arbitrary sizes */ 32 | #define UTF_INVALID 0xFFFD 33 | #define UTF_SIZ 4 34 | #define ESC_BUF_SIZ (128*UTF_SIZ) 35 | #define ESC_ARG_SIZ 16 36 | #define STR_BUF_SIZ ESC_BUF_SIZ 37 | #define STR_ARG_SIZ ESC_ARG_SIZ 38 | 39 | /* macros */ 40 | #define IS_SET(flag) ((term.mode & (flag)) != 0) 41 | #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) 42 | #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 43 | #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 44 | #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 45 | 46 | #define TSCREEN term.screen[IS_SET(MODE_ALTSCREEN)] 47 | #define TLINEOFFSET(y) (((y) + TSCREEN.cur - TSCREEN.off + TSCREEN.size) % TSCREEN.size) 48 | #define TLINE(y) (TSCREEN.buffer[TLINEOFFSET(y)]) 49 | 50 | enum term_mode { 51 | MODE_WRAP = 1 << 0, 52 | MODE_INSERT = 1 << 1, 53 | MODE_ALTSCREEN = 1 << 2, 54 | MODE_CRLF = 1 << 3, 55 | MODE_ECHO = 1 << 4, 56 | MODE_PRINT = 1 << 5, 57 | MODE_UTF8 = 1 << 6, 58 | }; 59 | 60 | enum cursor_movement { 61 | CURSOR_SAVE, 62 | CURSOR_LOAD 63 | }; 64 | 65 | enum cursor_state { 66 | CURSOR_DEFAULT = 0, 67 | CURSOR_WRAPNEXT = 1, 68 | CURSOR_ORIGIN = 2 69 | }; 70 | 71 | enum charset { 72 | CS_GRAPHIC0, 73 | CS_GRAPHIC1, 74 | CS_UK, 75 | CS_USA, 76 | CS_MULTI, 77 | CS_GER, 78 | CS_FIN 79 | }; 80 | 81 | enum escape_state { 82 | ESC_START = 1, 83 | ESC_CSI = 2, 84 | ESC_STR = 4, /* DCS, OSC, PM, APC */ 85 | ESC_ALTCHARSET = 8, 86 | ESC_STR_END = 16, /* a final string was encountered */ 87 | ESC_TEST = 32, /* Enter in test mode */ 88 | ESC_UTF8 = 64, 89 | }; 90 | 91 | typedef struct { 92 | Glyph attr; /* current char attributes */ 93 | int x; 94 | int y; 95 | char state; 96 | } TCursor; 97 | 98 | typedef struct { 99 | int mode; 100 | int type; 101 | int snap; 102 | /* 103 | * Selection variables: 104 | * nb – normalized coordinates of the beginning of the selection 105 | * ne – normalized coordinates of the end of the selection 106 | * ob – original coordinates of the beginning of the selection 107 | * oe – original coordinates of the end of the selection 108 | */ 109 | struct { 110 | int x, y; 111 | } nb, ne, ob, oe; 112 | 113 | int alt; 114 | } Selection; 115 | 116 | /* Screen lines */ 117 | typedef struct { 118 | Line* buffer; /* ring buffer */ 119 | int size; /* size of buffer */ 120 | int cur; /* start of active screen */ 121 | int off; /* scrollback line offset */ 122 | TCursor sc; /* saved cursor */ 123 | } LineBuffer; 124 | 125 | /* Internal representation of the screen */ 126 | typedef struct { 127 | int row; /* nb row */ 128 | int col; /* nb col */ 129 | LineBuffer screen[2]; /* screen and alternate screen */ 130 | int linelen; /* allocated line length */ 131 | int *dirty; /* dirtyness of lines */ 132 | TCursor c; /* cursor */ 133 | int ocx; /* old cursor col */ 134 | int ocy; /* old cursor row */ 135 | int top; /* top scroll limit */ 136 | int bot; /* bottom scroll limit */ 137 | int mode; /* terminal mode flags */ 138 | int esc; /* escape state flags */ 139 | char trantbl[4]; /* charset table translation */ 140 | int charset; /* current charset */ 141 | int icharset; /* selected charset for sequence */ 142 | int *tabs; 143 | Rune lastc; /* last printed char outside of sequence, 0 if control */ 144 | } Term; 145 | 146 | /* CSI Escape sequence structs */ 147 | /* ESC '[' [[ [] [;]] []] */ 148 | typedef struct { 149 | char buf[ESC_BUF_SIZ]; /* raw string */ 150 | size_t len; /* raw string length */ 151 | char priv; 152 | int arg[ESC_ARG_SIZ]; 153 | int narg; /* nb of args */ 154 | char mode[2]; 155 | } CSIEscape; 156 | 157 | /* STR Escape sequence structs */ 158 | /* ESC type [[ [] [;]] ] ESC '\' */ 159 | typedef struct { 160 | char type; /* ESC type ... */ 161 | char *buf; /* allocated raw string */ 162 | size_t siz; /* allocation size */ 163 | size_t len; /* raw string length */ 164 | char *args[STR_ARG_SIZ]; 165 | int narg; /* nb of args */ 166 | } STREscape; 167 | 168 | static void execsh(char *, char **); 169 | static void stty(char **); 170 | static void sigchld(int); 171 | static void ttywriteraw(const char *, size_t); 172 | 173 | static void csidump(void); 174 | static void csihandle(void); 175 | static void csiparse(void); 176 | static void csireset(void); 177 | static void osc_color_response(int, int, int); 178 | static int eschandle(uchar); 179 | static void strdump(void); 180 | static void strhandle(void); 181 | static void strparse(void); 182 | static void strreset(void); 183 | 184 | static void tprinter(char *, size_t); 185 | static void tdumpsel(void); 186 | static void tdumpline(int); 187 | static void tdump(void); 188 | static void tclearregion(int, int, int, int); 189 | static void tcursor(int); 190 | static void tdeletechar(int); 191 | static void tdeleteline(int); 192 | static void tinsertblank(int); 193 | static void tinsertblankline(int); 194 | static int tlinelen(int); 195 | static void tmoveto(int, int); 196 | static void tmoveato(int, int); 197 | static void tnewline(int); 198 | static void tputtab(int); 199 | static void tputc(Rune); 200 | static void treset(void); 201 | static void tscrollup(int, int); 202 | static void tscrolldown(int, int); 203 | static void tsetattr(const int *, int); 204 | static void tsetchar(Rune, const Glyph *, int, int); 205 | static void tsetdirt(int, int); 206 | static void tsetscroll(int, int); 207 | static void tswapscreen(void); 208 | static void tsetmode(int, int, const int *, int); 209 | static int twrite(const char *, int, int); 210 | static void tfulldirt(void); 211 | static void tcontrolcode(uchar ); 212 | static void tdectest(char ); 213 | static void tdefutf8(char); 214 | static int32_t tdefcolor(const int *, int *, int); 215 | static void tdeftran(char); 216 | static void tstrsequence(uchar); 217 | 218 | static void drawregion(int, int, int, int); 219 | static void clearline(Line, Glyph, int, int); 220 | static Line ensureline(Line); 221 | 222 | static void selnormalize(void); 223 | static void selscroll(int, int); 224 | static void selsnap(int *, int *, int); 225 | 226 | static size_t utf8decode(const char *, Rune *, size_t); 227 | static Rune utf8decodebyte(char, size_t *); 228 | static char utf8encodebyte(Rune, size_t); 229 | static size_t utf8validate(Rune *, size_t); 230 | 231 | static char *base64dec(const char *); 232 | static char base64dec_getc(const char **); 233 | 234 | static ssize_t xwrite(int, const char *, size_t); 235 | 236 | /* Globals */ 237 | static Term term; 238 | static Selection sel; 239 | static CSIEscape csiescseq; 240 | static STREscape strescseq; 241 | static int iofd = 1; 242 | static int cmdfd; 243 | static pid_t pid; 244 | 245 | static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 246 | static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 247 | static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 248 | static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 249 | 250 | ssize_t 251 | xwrite(int fd, const char *s, size_t len) 252 | { 253 | size_t aux = len; 254 | ssize_t r; 255 | 256 | while (len > 0) { 257 | r = write(fd, s, len); 258 | if (r < 0) 259 | return r; 260 | len -= r; 261 | s += r; 262 | } 263 | 264 | return aux; 265 | } 266 | 267 | void * 268 | xmalloc(size_t len) 269 | { 270 | void *p; 271 | 272 | if (!(p = malloc(len))) 273 | die("malloc: %s\n", strerror(errno)); 274 | 275 | return p; 276 | } 277 | 278 | void * 279 | xrealloc(void *p, size_t len) 280 | { 281 | if ((p = realloc(p, len)) == NULL) 282 | die("realloc: %s\n", strerror(errno)); 283 | 284 | return p; 285 | } 286 | 287 | char * 288 | xstrdup(const char *s) 289 | { 290 | char *p; 291 | 292 | if ((p = strdup(s)) == NULL) 293 | die("strdup: %s\n", strerror(errno)); 294 | 295 | return p; 296 | } 297 | 298 | size_t 299 | utf8decode(const char *c, Rune *u, size_t clen) 300 | { 301 | size_t i, j, len, type; 302 | Rune udecoded; 303 | 304 | *u = UTF_INVALID; 305 | if (!clen) 306 | return 0; 307 | udecoded = utf8decodebyte(c[0], &len); 308 | if (!BETWEEN(len, 1, UTF_SIZ)) 309 | return 1; 310 | for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 311 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 312 | if (type != 0) 313 | return j; 314 | } 315 | if (j < len) 316 | return 0; 317 | *u = udecoded; 318 | utf8validate(u, len); 319 | 320 | return len; 321 | } 322 | 323 | Rune 324 | utf8decodebyte(char c, size_t *i) 325 | { 326 | for (*i = 0; *i < LEN(utfmask); ++(*i)) 327 | if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 328 | return (uchar)c & ~utfmask[*i]; 329 | 330 | return 0; 331 | } 332 | 333 | size_t 334 | utf8encode(Rune u, char *c) 335 | { 336 | size_t len, i; 337 | 338 | len = utf8validate(&u, 0); 339 | if (len > UTF_SIZ) 340 | return 0; 341 | 342 | for (i = len - 1; i != 0; --i) { 343 | c[i] = utf8encodebyte(u, 0); 344 | u >>= 6; 345 | } 346 | c[0] = utf8encodebyte(u, len); 347 | 348 | return len; 349 | } 350 | 351 | char 352 | utf8encodebyte(Rune u, size_t i) 353 | { 354 | return utfbyte[i] | (u & ~utfmask[i]); 355 | } 356 | 357 | size_t 358 | utf8validate(Rune *u, size_t i) 359 | { 360 | if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 361 | *u = UTF_INVALID; 362 | for (i = 1; *u > utfmax[i]; ++i) 363 | ; 364 | 365 | return i; 366 | } 367 | 368 | char 369 | base64dec_getc(const char **src) 370 | { 371 | while (**src && !isprint((unsigned char)**src)) 372 | (*src)++; 373 | return **src ? *((*src)++) : '='; /* emulate padding if string ends */ 374 | } 375 | 376 | char * 377 | base64dec(const char *src) 378 | { 379 | size_t in_len = strlen(src); 380 | char *result, *dst; 381 | static const char base64_digits[256] = { 382 | [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 383 | 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 384 | 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 385 | 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 386 | 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 387 | }; 388 | 389 | if (in_len % 4) 390 | in_len += 4 - (in_len % 4); 391 | result = dst = xmalloc(in_len / 4 * 3 + 1); 392 | while (*src) { 393 | int a = base64_digits[(unsigned char) base64dec_getc(&src)]; 394 | int b = base64_digits[(unsigned char) base64dec_getc(&src)]; 395 | int c = base64_digits[(unsigned char) base64dec_getc(&src)]; 396 | int d = base64_digits[(unsigned char) base64dec_getc(&src)]; 397 | 398 | /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ 399 | if (a == -1 || b == -1) 400 | break; 401 | 402 | *dst++ = (a << 2) | ((b & 0x30) >> 4); 403 | if (c == -1) 404 | break; 405 | *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 406 | if (d == -1) 407 | break; 408 | *dst++ = ((c & 0x03) << 6) | d; 409 | } 410 | *dst = '\0'; 411 | return result; 412 | } 413 | 414 | void 415 | selinit(void) 416 | { 417 | sel.mode = SEL_IDLE; 418 | sel.snap = 0; 419 | sel.ob.x = -1; 420 | } 421 | 422 | int 423 | tlinelen(int y) 424 | { 425 | int i = term.col; 426 | Line line = TLINE(y); 427 | 428 | if (line[i - 1].mode & ATTR_WRAP) 429 | return i; 430 | 431 | while (i > 0 && line[i - 1].u == ' ') 432 | --i; 433 | 434 | return i; 435 | } 436 | 437 | void 438 | selstart(int col, int row, int snap) 439 | { 440 | selclear(); 441 | sel.mode = SEL_EMPTY; 442 | sel.type = SEL_REGULAR; 443 | sel.alt = IS_SET(MODE_ALTSCREEN); 444 | sel.snap = snap; 445 | sel.oe.x = sel.ob.x = col; 446 | sel.oe.y = sel.ob.y = row; 447 | selnormalize(); 448 | 449 | if (sel.snap != 0) 450 | sel.mode = SEL_READY; 451 | tsetdirt(sel.nb.y, sel.ne.y); 452 | } 453 | 454 | void 455 | selextend(int col, int row, int type, int done) 456 | { 457 | int oldey, oldex, oldsby, oldsey, oldtype; 458 | 459 | if (sel.mode == SEL_IDLE) 460 | return; 461 | if (done && sel.mode == SEL_EMPTY) { 462 | selclear(); 463 | return; 464 | } 465 | 466 | oldey = sel.oe.y; 467 | oldex = sel.oe.x; 468 | oldsby = sel.nb.y; 469 | oldsey = sel.ne.y; 470 | oldtype = sel.type; 471 | 472 | sel.oe.x = col; 473 | sel.oe.y = row; 474 | selnormalize(); 475 | sel.type = type; 476 | 477 | if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 478 | tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 479 | 480 | sel.mode = done ? SEL_IDLE : SEL_READY; 481 | } 482 | 483 | void 484 | selnormalize(void) 485 | { 486 | int i; 487 | 488 | if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 489 | sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 490 | sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 491 | } else { 492 | sel.nb.x = MIN(sel.ob.x, sel.oe.x); 493 | sel.ne.x = MAX(sel.ob.x, sel.oe.x); 494 | } 495 | sel.nb.y = MIN(sel.ob.y, sel.oe.y); 496 | sel.ne.y = MAX(sel.ob.y, sel.oe.y); 497 | 498 | selsnap(&sel.nb.x, &sel.nb.y, -1); 499 | selsnap(&sel.ne.x, &sel.ne.y, +1); 500 | 501 | /* expand selection over line breaks */ 502 | if (sel.type == SEL_RECTANGULAR) 503 | return; 504 | i = tlinelen(sel.nb.y); 505 | if (i < sel.nb.x) 506 | sel.nb.x = i; 507 | if (tlinelen(sel.ne.y) <= sel.ne.x) 508 | sel.ne.x = term.col - 1; 509 | } 510 | 511 | int 512 | selected(int x, int y) 513 | { 514 | if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || 515 | sel.alt != IS_SET(MODE_ALTSCREEN)) 516 | return 0; 517 | 518 | if (sel.type == SEL_RECTANGULAR) 519 | return BETWEEN(y, sel.nb.y, sel.ne.y) 520 | && BETWEEN(x, sel.nb.x, sel.ne.x); 521 | 522 | return BETWEEN(y, sel.nb.y, sel.ne.y) 523 | && (y != sel.nb.y || x >= sel.nb.x) 524 | && (y != sel.ne.y || x <= sel.ne.x); 525 | } 526 | 527 | void 528 | selsnap(int *x, int *y, int direction) 529 | { 530 | int newx, newy, xt, yt; 531 | int delim, prevdelim; 532 | const Glyph *gp, *prevgp; 533 | 534 | switch (sel.snap) { 535 | case SNAP_WORD: 536 | /* 537 | * Snap around if the word wraps around at the end or 538 | * beginning of a line. 539 | */ 540 | prevgp = &TLINE(*y)[*x]; 541 | prevdelim = ISDELIM(prevgp->u); 542 | for (;;) { 543 | newx = *x + direction; 544 | newy = *y; 545 | if (!BETWEEN(newx, 0, term.col - 1)) { 546 | newy += direction; 547 | newx = (newx + term.col) % term.col; 548 | if (!BETWEEN(newy, 0, term.row - 1)) 549 | break; 550 | 551 | if (direction > 0) 552 | yt = *y, xt = *x; 553 | else 554 | yt = newy, xt = newx; 555 | if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 556 | break; 557 | } 558 | 559 | if (newx >= tlinelen(newy)) 560 | break; 561 | 562 | gp = &TLINE(newy)[newx]; 563 | delim = ISDELIM(gp->u); 564 | if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 565 | || (delim && gp->u != prevgp->u))) 566 | break; 567 | 568 | *x = newx; 569 | *y = newy; 570 | prevgp = gp; 571 | prevdelim = delim; 572 | } 573 | break; 574 | case SNAP_LINE: 575 | /* 576 | * Snap around if the the previous line or the current one 577 | * has set ATTR_WRAP at its end. Then the whole next or 578 | * previous line will be selected. 579 | */ 580 | *x = (direction < 0) ? 0 : term.col - 1; 581 | if (direction < 0) { 582 | for (; *y > 0; *y += direction) { 583 | if (!(TLINE(*y-1)[term.col-1].mode 584 | & ATTR_WRAP)) { 585 | break; 586 | } 587 | } 588 | } else if (direction > 0) { 589 | for (; *y < term.row-1; *y += direction) { 590 | if (!(TLINE(*y)[term.col-1].mode 591 | & ATTR_WRAP)) { 592 | break; 593 | } 594 | } 595 | } 596 | break; 597 | } 598 | } 599 | 600 | char * 601 | getsel(void) 602 | { 603 | char *str, *ptr; 604 | int y, bufsize, lastx, linelen; 605 | const Glyph *gp, *last; 606 | 607 | if (sel.ob.x == -1) 608 | return NULL; 609 | 610 | bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 611 | ptr = str = xmalloc(bufsize); 612 | 613 | /* append every set & selected glyph to the selection */ 614 | for (y = sel.nb.y; y <= sel.ne.y; y++) { 615 | if ((linelen = tlinelen(y)) == 0) { 616 | *ptr++ = '\n'; 617 | continue; 618 | } 619 | 620 | if (sel.type == SEL_RECTANGULAR) { 621 | gp = &TLINE(y)[sel.nb.x]; 622 | lastx = sel.ne.x; 623 | } else { 624 | gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; 625 | lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 626 | } 627 | last = &TLINE(y)[MIN(lastx, linelen-1)]; 628 | while (last >= gp && last->u == ' ') 629 | --last; 630 | 631 | for ( ; gp <= last; ++gp) { 632 | if (gp->mode & ATTR_WDUMMY) 633 | continue; 634 | 635 | ptr += utf8encode(gp->u, ptr); 636 | } 637 | 638 | /* 639 | * Copy and pasting of line endings is inconsistent 640 | * in the inconsistent terminal and GUI world. 641 | * The best solution seems like to produce '\n' when 642 | * something is copied from st and convert '\n' to 643 | * '\r', when something to be pasted is received by 644 | * st. 645 | * FIXME: Fix the computer world. 646 | */ 647 | if ((y < sel.ne.y || lastx >= linelen) && 648 | (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) 649 | *ptr++ = '\n'; 650 | } 651 | *ptr = 0; 652 | return str; 653 | } 654 | 655 | void 656 | selclear(void) 657 | { 658 | if (sel.ob.x == -1) 659 | return; 660 | sel.mode = SEL_IDLE; 661 | sel.ob.x = -1; 662 | tsetdirt(sel.nb.y, sel.ne.y); 663 | } 664 | 665 | void 666 | die(const char *errstr, ...) 667 | { 668 | va_list ap; 669 | 670 | va_start(ap, errstr); 671 | vfprintf(stderr, errstr, ap); 672 | va_end(ap); 673 | exit(1); 674 | } 675 | 676 | void 677 | execsh(char *cmd, char **args) 678 | { 679 | char *sh, *prog, *arg; 680 | const struct passwd *pw; 681 | 682 | errno = 0; 683 | if ((pw = getpwuid(getuid())) == NULL) { 684 | if (errno) 685 | die("getpwuid: %s\n", strerror(errno)); 686 | else 687 | die("who are you?\n"); 688 | } 689 | 690 | if ((sh = getenv("SHELL")) == NULL) 691 | sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; 692 | 693 | if (args) { 694 | prog = args[0]; 695 | arg = NULL; 696 | } else if (scroll) { 697 | prog = scroll; 698 | arg = utmp ? utmp : sh; 699 | } else if (utmp) { 700 | prog = utmp; 701 | arg = NULL; 702 | } else { 703 | prog = sh; 704 | arg = NULL; 705 | } 706 | DEFAULT(args, ((char *[]) {prog, arg, NULL})); 707 | 708 | unsetenv("COLUMNS"); 709 | unsetenv("LINES"); 710 | unsetenv("TERMCAP"); 711 | setenv("LOGNAME", pw->pw_name, 1); 712 | setenv("USER", pw->pw_name, 1); 713 | setenv("SHELL", sh, 1); 714 | setenv("HOME", pw->pw_dir, 1); 715 | setenv("TERM", termname, 1); 716 | 717 | signal(SIGCHLD, SIG_DFL); 718 | signal(SIGHUP, SIG_DFL); 719 | signal(SIGINT, SIG_DFL); 720 | signal(SIGQUIT, SIG_DFL); 721 | signal(SIGTERM, SIG_DFL); 722 | signal(SIGALRM, SIG_DFL); 723 | 724 | execvp(prog, args); 725 | _exit(1); 726 | } 727 | 728 | void 729 | sigchld(int a) 730 | { 731 | int stat; 732 | pid_t p; 733 | 734 | if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 735 | die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 736 | 737 | if (pid != p) 738 | return; 739 | 740 | if (WIFEXITED(stat) && WEXITSTATUS(stat)) 741 | die("child exited with status %d\n", WEXITSTATUS(stat)); 742 | else if (WIFSIGNALED(stat)) 743 | die("child terminated due to signal %d\n", WTERMSIG(stat)); 744 | _exit(0); 745 | } 746 | 747 | void 748 | stty(char **args) 749 | { 750 | char cmd[_POSIX_ARG_MAX], **p, *q, *s; 751 | size_t n, siz; 752 | 753 | if ((n = strlen(stty_args)) > sizeof(cmd)-1) 754 | die("incorrect stty parameters\n"); 755 | memcpy(cmd, stty_args, n); 756 | q = cmd + n; 757 | siz = sizeof(cmd) - n; 758 | for (p = args; p && (s = *p); ++p) { 759 | if ((n = strlen(s)) > siz-1) 760 | die("stty parameter length too long\n"); 761 | *q++ = ' '; 762 | memcpy(q, s, n); 763 | q += n; 764 | siz -= n + 1; 765 | } 766 | *q = '\0'; 767 | if (system(cmd) != 0) 768 | perror("Couldn't call stty"); 769 | } 770 | 771 | int 772 | ttynew(const char *line, char *cmd, const char *out, char **args) 773 | { 774 | int m, s; 775 | 776 | if (out) { 777 | term.mode |= MODE_PRINT; 778 | iofd = (!strcmp(out, "-")) ? 779 | 1 : open(out, O_WRONLY | O_CREAT, 0666); 780 | if (iofd < 0) { 781 | fprintf(stderr, "Error opening %s:%s\n", 782 | out, strerror(errno)); 783 | } 784 | } 785 | 786 | if (line) { 787 | if ((cmdfd = open(line, O_RDWR)) < 0) 788 | die("open line '%s' failed: %s\n", 789 | line, strerror(errno)); 790 | dup2(cmdfd, 0); 791 | stty(args); 792 | return cmdfd; 793 | } 794 | 795 | /* seems to work fine on linux, openbsd and freebsd */ 796 | if (openpty(&m, &s, NULL, NULL, NULL) < 0) 797 | die("openpty failed: %s\n", strerror(errno)); 798 | 799 | switch (pid = fork()) { 800 | case -1: 801 | die("fork failed: %s\n", strerror(errno)); 802 | break; 803 | case 0: 804 | close(iofd); 805 | close(m); 806 | setsid(); /* create a new process group */ 807 | dup2(s, 0); 808 | dup2(s, 1); 809 | dup2(s, 2); 810 | if (ioctl(s, TIOCSCTTY, NULL) < 0) 811 | die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 812 | if (s > 2) 813 | close(s); 814 | #ifdef __OpenBSD__ 815 | if (pledge("stdio getpw proc exec", NULL) == -1) 816 | die("pledge\n"); 817 | #endif 818 | execsh(cmd, args); 819 | break; 820 | default: 821 | #ifdef __OpenBSD__ 822 | if (pledge("stdio rpath tty proc", NULL) == -1) 823 | die("pledge\n"); 824 | #endif 825 | close(s); 826 | cmdfd = m; 827 | signal(SIGCHLD, sigchld); 828 | break; 829 | } 830 | return cmdfd; 831 | } 832 | 833 | size_t 834 | ttyread(void) 835 | { 836 | static char buf[BUFSIZ]; 837 | static int buflen = 0; 838 | int ret, written; 839 | 840 | /* append read bytes to unprocessed bytes */ 841 | ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); 842 | 843 | switch (ret) { 844 | case 0: 845 | exit(0); 846 | case -1: 847 | die("couldn't read from shell: %s\n", strerror(errno)); 848 | default: 849 | buflen += ret; 850 | written = twrite(buf, buflen, 0); 851 | buflen -= written; 852 | /* keep any incomplete UTF-8 byte sequence for the next call */ 853 | if (buflen > 0) 854 | memmove(buf, buf + written, buflen); 855 | return ret; 856 | } 857 | } 858 | 859 | void 860 | ttywrite(const char *s, size_t n, int may_echo) 861 | { 862 | const char *next; 863 | 864 | if (may_echo && IS_SET(MODE_ECHO)) 865 | twrite(s, n, 1); 866 | 867 | if (!IS_SET(MODE_CRLF)) { 868 | ttywriteraw(s, n); 869 | return; 870 | } 871 | 872 | /* This is similar to how the kernel handles ONLCR for ttys */ 873 | while (n > 0) { 874 | if (*s == '\r') { 875 | next = s + 1; 876 | ttywriteraw("\r\n", 2); 877 | } else { 878 | next = memchr(s, '\r', n); 879 | DEFAULT(next, s + n); 880 | ttywriteraw(s, next - s); 881 | } 882 | n -= next - s; 883 | s = next; 884 | } 885 | } 886 | 887 | void 888 | ttywriteraw(const char *s, size_t n) 889 | { 890 | fd_set wfd, rfd; 891 | ssize_t r; 892 | size_t lim = 256; 893 | 894 | /* 895 | * Remember that we are using a pty, which might be a modem line. 896 | * Writing too much will clog the line. That's why we are doing this 897 | * dance. 898 | * FIXME: Migrate the world to Plan 9. 899 | */ 900 | while (n > 0) { 901 | FD_ZERO(&wfd); 902 | FD_ZERO(&rfd); 903 | FD_SET(cmdfd, &wfd); 904 | FD_SET(cmdfd, &rfd); 905 | 906 | /* Check if we can write. */ 907 | if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 908 | if (errno == EINTR) 909 | continue; 910 | die("select failed: %s\n", strerror(errno)); 911 | } 912 | if (FD_ISSET(cmdfd, &wfd)) { 913 | /* 914 | * Only write the bytes written by ttywrite() or the 915 | * default of 256. This seems to be a reasonable value 916 | * for a serial line. Bigger values might clog the I/O. 917 | */ 918 | if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) 919 | goto write_error; 920 | if (r < n) { 921 | /* 922 | * We weren't able to write out everything. 923 | * This means the buffer is getting full 924 | * again. Empty it. 925 | */ 926 | if (n < lim) 927 | lim = ttyread(); 928 | n -= r; 929 | s += r; 930 | } else { 931 | /* All bytes have been written. */ 932 | break; 933 | } 934 | } 935 | if (FD_ISSET(cmdfd, &rfd)) 936 | lim = ttyread(); 937 | } 938 | return; 939 | 940 | write_error: 941 | die("write error on tty: %s\n", strerror(errno)); 942 | } 943 | 944 | void 945 | ttyresize(int tw, int th) 946 | { 947 | struct winsize w; 948 | 949 | w.ws_row = term.row; 950 | w.ws_col = term.col; 951 | w.ws_xpixel = tw; 952 | w.ws_ypixel = th; 953 | if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) 954 | fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 955 | } 956 | 957 | void 958 | ttyhangup(void) 959 | { 960 | /* Send SIGHUP to shell */ 961 | kill(pid, SIGHUP); 962 | } 963 | 964 | int 965 | tattrset(int attr) 966 | { 967 | int i, j; 968 | int y = TLINEOFFSET(0); 969 | 970 | for (i = 0; i < term.row-1; i++) { 971 | Line line = TSCREEN.buffer[y]; 972 | for (j = 0; j < term.col-1; j++) { 973 | if (line[j].mode & attr) 974 | return 1; 975 | } 976 | y = (y+1) % TSCREEN.size; 977 | } 978 | 979 | return 0; 980 | } 981 | 982 | void 983 | tsetdirt(int top, int bot) 984 | { 985 | int i; 986 | 987 | LIMIT(top, 0, term.row-1); 988 | LIMIT(bot, 0, term.row-1); 989 | 990 | for (i = top; i <= bot; i++) 991 | term.dirty[i] = 1; 992 | } 993 | 994 | void 995 | tsetdirtattr(int attr) 996 | { 997 | int i, j; 998 | int y = TLINEOFFSET(0); 999 | 1000 | for (i = 0; i < term.row-1; i++) { 1001 | Line line = TSCREEN.buffer[y]; 1002 | for (j = 0; j < term.col-1; j++) { 1003 | if (line[j].mode & attr) { 1004 | tsetdirt(i, i); 1005 | break; 1006 | } 1007 | } 1008 | y = (y+1) % TSCREEN.size; 1009 | } 1010 | } 1011 | 1012 | void 1013 | tfulldirt(void) 1014 | { 1015 | tsetdirt(0, term.row-1); 1016 | } 1017 | 1018 | void 1019 | tcursor(int mode) 1020 | { 1021 | if (mode == CURSOR_SAVE) { 1022 | TSCREEN.sc = term.c; 1023 | } else if (mode == CURSOR_LOAD) { 1024 | term.c = TSCREEN.sc; 1025 | tmoveto(term.c.x, term.c.y); 1026 | } 1027 | } 1028 | 1029 | void 1030 | treset(void) 1031 | { 1032 | int i, j; 1033 | Glyph g = (Glyph){ .fg = defaultfg, .bg = defaultbg}; 1034 | 1035 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1036 | for (i = tabspaces; i < term.col; i += tabspaces) 1037 | term.tabs[i] = 1; 1038 | term.top = 0; 1039 | term.bot = term.row - 1; 1040 | term.mode = MODE_WRAP|MODE_UTF8; 1041 | memset(term.trantbl, CS_USA, sizeof(term.trantbl)); 1042 | term.charset = 0; 1043 | 1044 | for (i = 0; i < 2; i++) { 1045 | term.screen[i].sc = (TCursor){{ 1046 | .fg = defaultfg, 1047 | .bg = defaultbg 1048 | }}; 1049 | term.screen[i].cur = 0; 1050 | term.screen[i].off = 0; 1051 | for (j = 0; j < term.row; ++j) { 1052 | if (term.col != term.linelen) 1053 | term.screen[i].buffer[j] = xrealloc(term.screen[i].buffer[j], term.col * sizeof(Glyph)); 1054 | clearline(term.screen[i].buffer[j], g, 0, term.col); 1055 | } 1056 | for (j = term.row; j < term.screen[i].size; ++j) { 1057 | free(term.screen[i].buffer[j]); 1058 | term.screen[i].buffer[j] = NULL; 1059 | } 1060 | } 1061 | tcursor(CURSOR_LOAD); 1062 | term.linelen = term.col; 1063 | tfulldirt(); 1064 | } 1065 | 1066 | void 1067 | tnew(int col, int row) 1068 | { 1069 | int i; 1070 | term = (Term){}; 1071 | term.screen[0].buffer = xmalloc(HISTSIZE * sizeof(Line)); 1072 | term.screen[0].size = HISTSIZE; 1073 | term.screen[1].buffer = NULL; 1074 | for (i = 0; i < HISTSIZE; ++i) term.screen[0].buffer[i] = NULL; 1075 | 1076 | tresize(col, row); 1077 | treset(); 1078 | } 1079 | 1080 | void 1081 | tswapscreen(void) 1082 | { 1083 | term.mode ^= MODE_ALTSCREEN; 1084 | tfulldirt(); 1085 | } 1086 | 1087 | void 1088 | kscrollup(const Arg *a) 1089 | { 1090 | int n = a->i; 1091 | 1092 | if (IS_SET(MODE_ALTSCREEN)) 1093 | return; 1094 | 1095 | if (n < 0) n = (-n) * term.row; 1096 | if (n > TSCREEN.size - term.row - TSCREEN.off) n = TSCREEN.size - term.row - TSCREEN.off; 1097 | while (!TLINE(-n)) --n; 1098 | TSCREEN.off += n; 1099 | selscroll(0, n); 1100 | tfulldirt(); 1101 | } 1102 | 1103 | void 1104 | kscrolldown(const Arg *a) 1105 | { 1106 | 1107 | int n = a->i; 1108 | 1109 | if (IS_SET(MODE_ALTSCREEN)) 1110 | return; 1111 | 1112 | if (n < 0) n = (-n) * term.row; 1113 | if (n > TSCREEN.off) n = TSCREEN.off; 1114 | TSCREEN.off -= n; 1115 | selscroll(0, -n); 1116 | tfulldirt(); 1117 | } 1118 | 1119 | void 1120 | tscrolldown(int orig, int n) 1121 | { 1122 | int i; 1123 | Line temp; 1124 | 1125 | LIMIT(n, 0, term.bot-orig+1); 1126 | 1127 | /* Ensure that lines are allocated */ 1128 | for (i = -n; i < 0; i++) { 1129 | TLINE(i) = ensureline(TLINE(i)); 1130 | } 1131 | 1132 | /* Shift non-scrolling areas in ring buffer */ 1133 | for (i = term.bot+1; i < term.row; i++) { 1134 | temp = TLINE(i); 1135 | TLINE(i) = TLINE(i-n); 1136 | TLINE(i-n) = temp; 1137 | } 1138 | for (i = 0; i < orig; i++) { 1139 | temp = TLINE(i); 1140 | TLINE(i) = TLINE(i-n); 1141 | TLINE(i-n) = temp; 1142 | } 1143 | 1144 | /* Scroll buffer */ 1145 | TSCREEN.cur = (TSCREEN.cur + TSCREEN.size - n) % TSCREEN.size; 1146 | /* Clear lines that have entered the view */ 1147 | tclearregion(0, orig, term.linelen-1, orig+n-1); 1148 | /* Redraw portion of the screen that has scrolled */ 1149 | tsetdirt(orig+n-1, term.bot); 1150 | selscroll(orig, n); 1151 | } 1152 | 1153 | void 1154 | tscrollup(int orig, int n) 1155 | { 1156 | int i; 1157 | Line temp; 1158 | 1159 | LIMIT(n, 0, term.bot-orig+1); 1160 | 1161 | /* Ensure that lines are allocated */ 1162 | for (i = term.row; i < term.row + n; i++) { 1163 | TLINE(i) = ensureline(TLINE(i)); 1164 | } 1165 | 1166 | /* Shift non-scrolling areas in ring buffer */ 1167 | for (i = orig-1; i >= 0; i--) { 1168 | temp = TLINE(i); 1169 | TLINE(i) = TLINE(i+n); 1170 | TLINE(i+n) = temp; 1171 | } 1172 | for (i = term.row-1; i >term.bot; i--) { 1173 | temp = TLINE(i); 1174 | TLINE(i) = TLINE(i+n); 1175 | TLINE(i+n) = temp; 1176 | } 1177 | 1178 | /* Scroll buffer */ 1179 | TSCREEN.cur = (TSCREEN.cur + n) % TSCREEN.size; 1180 | /* Clear lines that have entered the view */ 1181 | tclearregion(0, term.bot-n+1, term.linelen-1, term.bot); 1182 | /* Redraw portion of the screen that has scrolled */ 1183 | tsetdirt(orig, term.bot-n+1); 1184 | selscroll(orig, -n); 1185 | } 1186 | 1187 | void 1188 | selscroll(int orig, int n) 1189 | { 1190 | if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) 1191 | return; 1192 | 1193 | if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { 1194 | selclear(); 1195 | } else if (BETWEEN(sel.nb.y, orig, term.bot)) { 1196 | sel.ob.y += n; 1197 | sel.oe.y += n; 1198 | if (sel.ob.y < term.top || sel.ob.y > term.bot || 1199 | sel.oe.y < term.top || sel.oe.y > term.bot) { 1200 | selclear(); 1201 | } else { 1202 | selnormalize(); 1203 | } 1204 | } 1205 | } 1206 | 1207 | void 1208 | tnewline(int first_col) 1209 | { 1210 | int y = term.c.y; 1211 | 1212 | if (y == term.bot) { 1213 | tscrollup(term.top, 1); 1214 | } else { 1215 | y++; 1216 | } 1217 | tmoveto(first_col ? 0 : term.c.x, y); 1218 | } 1219 | 1220 | void 1221 | csiparse(void) 1222 | { 1223 | char *p = csiescseq.buf, *np; 1224 | long int v; 1225 | int sep = ';'; /* colon or semi-colon, but not both */ 1226 | 1227 | csiescseq.narg = 0; 1228 | if (*p == '?') { 1229 | csiescseq.priv = 1; 1230 | p++; 1231 | } 1232 | 1233 | csiescseq.buf[csiescseq.len] = '\0'; 1234 | while (p < csiescseq.buf+csiescseq.len) { 1235 | np = NULL; 1236 | v = strtol(p, &np, 10); 1237 | if (np == p) 1238 | v = 0; 1239 | if (v == LONG_MAX || v == LONG_MIN) 1240 | v = -1; 1241 | csiescseq.arg[csiescseq.narg++] = v; 1242 | p = np; 1243 | if (sep == ';' && *p == ':') 1244 | sep = ':'; /* allow override to colon once */ 1245 | if (*p != sep || csiescseq.narg == ESC_ARG_SIZ) 1246 | break; 1247 | p++; 1248 | } 1249 | csiescseq.mode[0] = *p++; 1250 | csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1251 | } 1252 | 1253 | /* for absolute user moves, when decom is set */ 1254 | void 1255 | tmoveato(int x, int y) 1256 | { 1257 | tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1258 | } 1259 | 1260 | void 1261 | tmoveto(int x, int y) 1262 | { 1263 | int miny, maxy; 1264 | 1265 | if (term.c.state & CURSOR_ORIGIN) { 1266 | miny = term.top; 1267 | maxy = term.bot; 1268 | } else { 1269 | miny = 0; 1270 | maxy = term.row - 1; 1271 | } 1272 | term.c.state &= ~CURSOR_WRAPNEXT; 1273 | term.c.x = LIMIT(x, 0, term.col-1); 1274 | term.c.y = LIMIT(y, miny, maxy); 1275 | } 1276 | 1277 | void 1278 | tsetchar(Rune u, const Glyph *attr, int x, int y) 1279 | { 1280 | static const char *vt100_0[62] = { /* 0x41 - 0x7e */ 1281 | "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1282 | 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1283 | 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1284 | 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1285 | "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1286 | "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1287 | "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1288 | "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1289 | }; 1290 | Line line = TLINE(y); 1291 | 1292 | /* 1293 | * The table is proudly stolen from rxvt. 1294 | */ 1295 | if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1296 | BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1297 | utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1298 | 1299 | if (line[x].mode & ATTR_WIDE) { 1300 | if (x+1 < term.col) { 1301 | line[x+1].u = ' '; 1302 | line[x+1].mode &= ~ATTR_WDUMMY; 1303 | } 1304 | } else if (line[x].mode & ATTR_WDUMMY) { 1305 | line[x-1].u = ' '; 1306 | line[x-1].mode &= ~ATTR_WIDE; 1307 | } 1308 | 1309 | term.dirty[y] = 1; 1310 | line[x] = *attr; 1311 | line[x].u = u; 1312 | } 1313 | 1314 | void 1315 | tclearregion(int x1, int y1, int x2, int y2) 1316 | { 1317 | int x, y, L, S, temp; 1318 | Glyph *gp; 1319 | 1320 | if (x1 > x2) 1321 | temp = x1, x1 = x2, x2 = temp; 1322 | if (y1 > y2) 1323 | temp = y1, y1 = y2, y2 = temp; 1324 | 1325 | LIMIT(x1, 0, term.linelen-1); 1326 | LIMIT(x2, 0, term.linelen-1); 1327 | LIMIT(y1, 0, term.row-1); 1328 | LIMIT(y2, 0, term.row-1); 1329 | 1330 | L = TLINEOFFSET(y1); 1331 | for (y = y1; y <= y2; y++) { 1332 | term.dirty[y] = 1; 1333 | for (x = x1; x <= x2; x++) { 1334 | gp = &TSCREEN.buffer[L][x]; 1335 | if (selected(x, y)) 1336 | selclear(); 1337 | gp->fg = term.c.attr.fg; 1338 | gp->bg = term.c.attr.bg; 1339 | gp->mode = 0; 1340 | gp->u = ' '; 1341 | } 1342 | L = (L + 1) % TSCREEN.size; 1343 | } 1344 | } 1345 | 1346 | void 1347 | tdeletechar(int n) 1348 | { 1349 | int dst, src, size; 1350 | Glyph *line; 1351 | 1352 | LIMIT(n, 0, term.col - term.c.x); 1353 | 1354 | dst = term.c.x; 1355 | src = term.c.x + n; 1356 | size = term.col - src; 1357 | line = TLINE(term.c.y); 1358 | 1359 | memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1360 | tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1361 | } 1362 | 1363 | void 1364 | tinsertblank(int n) 1365 | { 1366 | int dst, src, size; 1367 | Glyph *line; 1368 | 1369 | LIMIT(n, 0, term.col - term.c.x); 1370 | 1371 | dst = term.c.x + n; 1372 | src = term.c.x; 1373 | size = term.col - dst; 1374 | line = TLINE(term.c.y); 1375 | 1376 | memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1377 | tclearregion(src, term.c.y, dst - 1, term.c.y); 1378 | } 1379 | 1380 | void 1381 | tinsertblankline(int n) 1382 | { 1383 | if (BETWEEN(term.c.y, term.top, term.bot)) 1384 | tscrolldown(term.c.y, n); 1385 | } 1386 | 1387 | void 1388 | tdeleteline(int n) 1389 | { 1390 | if (BETWEEN(term.c.y, term.top, term.bot)) 1391 | tscrollup(term.c.y, n); 1392 | } 1393 | 1394 | int32_t 1395 | tdefcolor(const int *attr, int *npar, int l) 1396 | { 1397 | int32_t idx = -1; 1398 | uint r, g, b; 1399 | 1400 | switch (attr[*npar + 1]) { 1401 | case 2: /* direct color in RGB space */ 1402 | if (*npar + 4 >= l) { 1403 | fprintf(stderr, 1404 | "erresc(38): Incorrect number of parameters (%d)\n", 1405 | *npar); 1406 | break; 1407 | } 1408 | r = attr[*npar + 2]; 1409 | g = attr[*npar + 3]; 1410 | b = attr[*npar + 4]; 1411 | *npar += 4; 1412 | if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1413 | fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1414 | r, g, b); 1415 | else 1416 | idx = TRUECOLOR(r, g, b); 1417 | break; 1418 | case 5: /* indexed color */ 1419 | if (*npar + 2 >= l) { 1420 | fprintf(stderr, 1421 | "erresc(38): Incorrect number of parameters (%d)\n", 1422 | *npar); 1423 | break; 1424 | } 1425 | *npar += 2; 1426 | if (!BETWEEN(attr[*npar], 0, 255)) 1427 | fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1428 | else 1429 | idx = attr[*npar]; 1430 | break; 1431 | case 0: /* implemented defined (only foreground) */ 1432 | case 1: /* transparent */ 1433 | case 3: /* direct color in CMY space */ 1434 | case 4: /* direct color in CMYK space */ 1435 | default: 1436 | fprintf(stderr, 1437 | "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1438 | break; 1439 | } 1440 | 1441 | return idx; 1442 | } 1443 | 1444 | void 1445 | tsetattr(const int *attr, int l) 1446 | { 1447 | int i; 1448 | int32_t idx; 1449 | 1450 | for (i = 0; i < l; i++) { 1451 | switch (attr[i]) { 1452 | case 0: 1453 | term.c.attr.mode &= ~( 1454 | ATTR_BOLD | 1455 | ATTR_FAINT | 1456 | ATTR_ITALIC | 1457 | ATTR_UNDERLINE | 1458 | ATTR_BLINK | 1459 | ATTR_REVERSE | 1460 | ATTR_INVISIBLE | 1461 | ATTR_STRUCK ); 1462 | term.c.attr.fg = defaultfg; 1463 | term.c.attr.bg = defaultbg; 1464 | break; 1465 | case 1: 1466 | term.c.attr.mode |= ATTR_BOLD; 1467 | break; 1468 | case 2: 1469 | term.c.attr.mode |= ATTR_FAINT; 1470 | break; 1471 | case 3: 1472 | term.c.attr.mode |= ATTR_ITALIC; 1473 | break; 1474 | case 4: 1475 | term.c.attr.mode |= ATTR_UNDERLINE; 1476 | break; 1477 | case 5: /* slow blink */ 1478 | /* FALLTHROUGH */ 1479 | case 6: /* rapid blink */ 1480 | term.c.attr.mode |= ATTR_BLINK; 1481 | break; 1482 | case 7: 1483 | term.c.attr.mode |= ATTR_REVERSE; 1484 | break; 1485 | case 8: 1486 | term.c.attr.mode |= ATTR_INVISIBLE; 1487 | break; 1488 | case 9: 1489 | term.c.attr.mode |= ATTR_STRUCK; 1490 | break; 1491 | case 22: 1492 | term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1493 | break; 1494 | case 23: 1495 | term.c.attr.mode &= ~ATTR_ITALIC; 1496 | break; 1497 | case 24: 1498 | term.c.attr.mode &= ~ATTR_UNDERLINE; 1499 | break; 1500 | case 25: 1501 | term.c.attr.mode &= ~ATTR_BLINK; 1502 | break; 1503 | case 27: 1504 | term.c.attr.mode &= ~ATTR_REVERSE; 1505 | break; 1506 | case 28: 1507 | term.c.attr.mode &= ~ATTR_INVISIBLE; 1508 | break; 1509 | case 29: 1510 | term.c.attr.mode &= ~ATTR_STRUCK; 1511 | break; 1512 | case 38: 1513 | if ((idx = tdefcolor(attr, &i, l)) >= 0) 1514 | term.c.attr.fg = idx; 1515 | break; 1516 | case 39: 1517 | term.c.attr.fg = defaultfg; 1518 | break; 1519 | case 48: 1520 | if ((idx = tdefcolor(attr, &i, l)) >= 0) 1521 | term.c.attr.bg = idx; 1522 | break; 1523 | case 49: 1524 | term.c.attr.bg = defaultbg; 1525 | break; 1526 | default: 1527 | if (BETWEEN(attr[i], 30, 37)) { 1528 | term.c.attr.fg = attr[i] - 30; 1529 | } else if (BETWEEN(attr[i], 40, 47)) { 1530 | term.c.attr.bg = attr[i] - 40; 1531 | } else if (BETWEEN(attr[i], 90, 97)) { 1532 | term.c.attr.fg = attr[i] - 90 + 8; 1533 | } else if (BETWEEN(attr[i], 100, 107)) { 1534 | term.c.attr.bg = attr[i] - 100 + 8; 1535 | } else { 1536 | fprintf(stderr, 1537 | "erresc(default): gfx attr %d unknown\n", 1538 | attr[i]); 1539 | csidump(); 1540 | } 1541 | break; 1542 | } 1543 | } 1544 | } 1545 | 1546 | void 1547 | tsetscroll(int t, int b) 1548 | { 1549 | int temp; 1550 | 1551 | LIMIT(t, 0, term.row-1); 1552 | LIMIT(b, 0, term.row-1); 1553 | if (t > b) { 1554 | temp = t; 1555 | t = b; 1556 | b = temp; 1557 | } 1558 | term.top = t; 1559 | term.bot = b; 1560 | } 1561 | 1562 | void 1563 | tsetmode(int priv, int set, const int *args, int narg) 1564 | { 1565 | int alt; const int *lim; 1566 | 1567 | for (lim = args + narg; args < lim; ++args) { 1568 | if (priv) { 1569 | switch (*args) { 1570 | case 1: /* DECCKM -- Cursor key */ 1571 | xsetmode(set, MODE_APPCURSOR); 1572 | break; 1573 | case 5: /* DECSCNM -- Reverse video */ 1574 | xsetmode(set, MODE_REVERSE); 1575 | break; 1576 | case 6: /* DECOM -- Origin */ 1577 | MODBIT(term.c.state, set, CURSOR_ORIGIN); 1578 | tmoveato(0, 0); 1579 | break; 1580 | case 7: /* DECAWM -- Auto wrap */ 1581 | MODBIT(term.mode, set, MODE_WRAP); 1582 | break; 1583 | case 0: /* Error (IGNORED) */ 1584 | case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1585 | case 3: /* DECCOLM -- Column (IGNORED) */ 1586 | case 4: /* DECSCLM -- Scroll (IGNORED) */ 1587 | case 8: /* DECARM -- Auto repeat (IGNORED) */ 1588 | case 18: /* DECPFF -- Printer feed (IGNORED) */ 1589 | case 19: /* DECPEX -- Printer extent (IGNORED) */ 1590 | case 42: /* DECNRCM -- National characters (IGNORED) */ 1591 | case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1592 | break; 1593 | case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1594 | xsetmode(!set, MODE_HIDE); 1595 | break; 1596 | case 9: /* X10 mouse compatibility mode */ 1597 | xsetpointermotion(0); 1598 | xsetmode(0, MODE_MOUSE); 1599 | xsetmode(set, MODE_MOUSEX10); 1600 | break; 1601 | case 1000: /* 1000: report button press */ 1602 | xsetpointermotion(0); 1603 | xsetmode(0, MODE_MOUSE); 1604 | xsetmode(set, MODE_MOUSEBTN); 1605 | break; 1606 | case 1002: /* 1002: report motion on button press */ 1607 | xsetpointermotion(0); 1608 | xsetmode(0, MODE_MOUSE); 1609 | xsetmode(set, MODE_MOUSEMOTION); 1610 | break; 1611 | case 1003: /* 1003: enable all mouse motions */ 1612 | xsetpointermotion(set); 1613 | xsetmode(0, MODE_MOUSE); 1614 | xsetmode(set, MODE_MOUSEMANY); 1615 | break; 1616 | case 1004: /* 1004: send focus events to tty */ 1617 | xsetmode(set, MODE_FOCUS); 1618 | break; 1619 | case 1006: /* 1006: extended reporting mode */ 1620 | xsetmode(set, MODE_MOUSESGR); 1621 | break; 1622 | case 1034: 1623 | xsetmode(set, MODE_8BIT); 1624 | break; 1625 | case 1049: /* swap screen & set/restore cursor as xterm */ 1626 | if (!allowaltscreen) 1627 | break; 1628 | tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1629 | /* FALLTHROUGH */ 1630 | case 47: /* swap screen */ 1631 | case 1047: 1632 | if (!allowaltscreen) 1633 | break; 1634 | alt = IS_SET(MODE_ALTSCREEN); 1635 | if (alt) { 1636 | tclearregion(0, 0, term.col-1, 1637 | term.row-1); 1638 | } 1639 | if (set ^ alt) /* set is always 1 or 0 */ 1640 | tswapscreen(); 1641 | if (*args != 1049) 1642 | break; 1643 | /* FALLTHROUGH */ 1644 | case 1048: 1645 | tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1646 | break; 1647 | case 2004: /* 2004: bracketed paste mode */ 1648 | xsetmode(set, MODE_BRCKTPASTE); 1649 | break; 1650 | /* Not implemented mouse modes. See comments there. */ 1651 | case 1001: /* mouse highlight mode; can hang the 1652 | terminal by design when implemented. */ 1653 | case 1005: /* UTF-8 mouse mode; will confuse 1654 | applications not supporting UTF-8 1655 | and luit. */ 1656 | case 1015: /* urxvt mangled mouse mode; incompatible 1657 | and can be mistaken for other control 1658 | codes. */ 1659 | break; 1660 | default: 1661 | fprintf(stderr, 1662 | "erresc: unknown private set/reset mode %d\n", 1663 | *args); 1664 | break; 1665 | } 1666 | } else { 1667 | switch (*args) { 1668 | case 0: /* Error (IGNORED) */ 1669 | break; 1670 | case 2: 1671 | xsetmode(set, MODE_KBDLOCK); 1672 | break; 1673 | case 4: /* IRM -- Insertion-replacement */ 1674 | MODBIT(term.mode, set, MODE_INSERT); 1675 | break; 1676 | case 12: /* SRM -- Send/Receive */ 1677 | MODBIT(term.mode, !set, MODE_ECHO); 1678 | break; 1679 | case 20: /* LNM -- Linefeed/new line */ 1680 | MODBIT(term.mode, set, MODE_CRLF); 1681 | break; 1682 | default: 1683 | fprintf(stderr, 1684 | "erresc: unknown set/reset mode %d\n", 1685 | *args); 1686 | break; 1687 | } 1688 | } 1689 | } 1690 | } 1691 | 1692 | void 1693 | csihandle(void) 1694 | { 1695 | char buf[40]; 1696 | int len; 1697 | 1698 | switch (csiescseq.mode[0]) { 1699 | default: 1700 | unknown: 1701 | fprintf(stderr, "erresc: unknown csi "); 1702 | csidump(); 1703 | /* die(""); */ 1704 | break; 1705 | case '@': /* ICH -- Insert blank char */ 1706 | DEFAULT(csiescseq.arg[0], 1); 1707 | tinsertblank(csiescseq.arg[0]); 1708 | break; 1709 | case 'A': /* CUU -- Cursor Up */ 1710 | DEFAULT(csiescseq.arg[0], 1); 1711 | tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 1712 | break; 1713 | case 'B': /* CUD -- Cursor Down */ 1714 | case 'e': /* VPR --Cursor Down */ 1715 | DEFAULT(csiescseq.arg[0], 1); 1716 | tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 1717 | break; 1718 | case 'i': /* MC -- Media Copy */ 1719 | switch (csiescseq.arg[0]) { 1720 | case 0: 1721 | tdump(); 1722 | break; 1723 | case 1: 1724 | tdumpline(term.c.y); 1725 | break; 1726 | case 2: 1727 | tdumpsel(); 1728 | break; 1729 | case 4: 1730 | term.mode &= ~MODE_PRINT; 1731 | break; 1732 | case 5: 1733 | term.mode |= MODE_PRINT; 1734 | break; 1735 | } 1736 | break; 1737 | case 'c': /* DA -- Device Attributes */ 1738 | if (csiescseq.arg[0] == 0) 1739 | ttywrite(vtiden, strlen(vtiden), 0); 1740 | break; 1741 | case 'b': /* REP -- if last char is printable print it more times */ 1742 | LIMIT(csiescseq.arg[0], 1, 65535); 1743 | if (term.lastc) 1744 | while (csiescseq.arg[0]-- > 0) 1745 | tputc(term.lastc); 1746 | break; 1747 | case 'C': /* CUF -- Cursor Forward */ 1748 | case 'a': /* HPR -- Cursor Forward */ 1749 | DEFAULT(csiescseq.arg[0], 1); 1750 | tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 1751 | break; 1752 | case 'D': /* CUB -- Cursor Backward */ 1753 | DEFAULT(csiescseq.arg[0], 1); 1754 | tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 1755 | break; 1756 | case 'E': /* CNL -- Cursor Down and first col */ 1757 | DEFAULT(csiescseq.arg[0], 1); 1758 | tmoveto(0, term.c.y+csiescseq.arg[0]); 1759 | break; 1760 | case 'F': /* CPL -- Cursor Up and first col */ 1761 | DEFAULT(csiescseq.arg[0], 1); 1762 | tmoveto(0, term.c.y-csiescseq.arg[0]); 1763 | break; 1764 | case 'g': /* TBC -- Tabulation clear */ 1765 | switch (csiescseq.arg[0]) { 1766 | case 0: /* clear current tab stop */ 1767 | term.tabs[term.c.x] = 0; 1768 | break; 1769 | case 3: /* clear all the tabs */ 1770 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1771 | break; 1772 | default: 1773 | goto unknown; 1774 | } 1775 | break; 1776 | case 'G': /* CHA -- Move to */ 1777 | case '`': /* HPA */ 1778 | DEFAULT(csiescseq.arg[0], 1); 1779 | tmoveto(csiescseq.arg[0]-1, term.c.y); 1780 | break; 1781 | case 'H': /* CUP -- Move to */ 1782 | case 'f': /* HVP */ 1783 | DEFAULT(csiescseq.arg[0], 1); 1784 | DEFAULT(csiescseq.arg[1], 1); 1785 | tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 1786 | break; 1787 | case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ 1788 | DEFAULT(csiescseq.arg[0], 1); 1789 | tputtab(csiescseq.arg[0]); 1790 | break; 1791 | case 'J': /* ED -- Clear screen */ 1792 | switch (csiescseq.arg[0]) { 1793 | case 0: /* below */ 1794 | tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 1795 | if (term.c.y < term.row-1) { 1796 | tclearregion(0, term.c.y+1, term.col-1, 1797 | term.row-1); 1798 | } 1799 | break; 1800 | case 1: /* above */ 1801 | if (term.c.y > 1) 1802 | tclearregion(0, 0, term.col-1, term.c.y-1); 1803 | tclearregion(0, term.c.y, term.c.x, term.c.y); 1804 | break; 1805 | case 2: /* all */ 1806 | tclearregion(0, 0, term.col-1, term.row-1); 1807 | break; 1808 | default: 1809 | goto unknown; 1810 | } 1811 | break; 1812 | case 'K': /* EL -- Clear line */ 1813 | switch (csiescseq.arg[0]) { 1814 | case 0: /* right */ 1815 | tclearregion(term.c.x, term.c.y, term.col-1, 1816 | term.c.y); 1817 | break; 1818 | case 1: /* left */ 1819 | tclearregion(0, term.c.y, term.c.x, term.c.y); 1820 | break; 1821 | case 2: /* all */ 1822 | tclearregion(0, term.c.y, term.col-1, term.c.y); 1823 | break; 1824 | } 1825 | break; 1826 | case 'S': /* SU -- Scroll line up */ 1827 | if (csiescseq.priv) break; 1828 | DEFAULT(csiescseq.arg[0], 1); 1829 | tscrollup(term.top, csiescseq.arg[0]); 1830 | break; 1831 | case 'T': /* SD -- Scroll line down */ 1832 | DEFAULT(csiescseq.arg[0], 1); 1833 | tscrolldown(term.top, csiescseq.arg[0]); 1834 | break; 1835 | case 'L': /* IL -- Insert blank lines */ 1836 | DEFAULT(csiescseq.arg[0], 1); 1837 | tinsertblankline(csiescseq.arg[0]); 1838 | break; 1839 | case 'l': /* RM -- Reset Mode */ 1840 | tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1841 | break; 1842 | case 'M': /* DL -- Delete lines */ 1843 | DEFAULT(csiescseq.arg[0], 1); 1844 | tdeleteline(csiescseq.arg[0]); 1845 | break; 1846 | case 'X': /* ECH -- Erase char */ 1847 | DEFAULT(csiescseq.arg[0], 1); 1848 | tclearregion(term.c.x, term.c.y, 1849 | term.c.x + csiescseq.arg[0] - 1, term.c.y); 1850 | break; 1851 | case 'P': /* DCH -- Delete char */ 1852 | DEFAULT(csiescseq.arg[0], 1); 1853 | tdeletechar(csiescseq.arg[0]); 1854 | break; 1855 | case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ 1856 | DEFAULT(csiescseq.arg[0], 1); 1857 | tputtab(-csiescseq.arg[0]); 1858 | break; 1859 | case 'd': /* VPA -- Move to */ 1860 | DEFAULT(csiescseq.arg[0], 1); 1861 | tmoveato(term.c.x, csiescseq.arg[0]-1); 1862 | break; 1863 | case 'h': /* SM -- Set terminal mode */ 1864 | tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1865 | break; 1866 | case 'm': /* SGR -- Terminal attribute (color) */ 1867 | tsetattr(csiescseq.arg, csiescseq.narg); 1868 | break; 1869 | case 'n': /* DSR -- Device Status Report */ 1870 | switch (csiescseq.arg[0]) { 1871 | case 5: /* Status Report "OK" `0n` */ 1872 | ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); 1873 | break; 1874 | case 6: /* Report Cursor Position (CPR) ";R" */ 1875 | len = snprintf(buf, sizeof(buf), "\033[%i;%iR", 1876 | term.c.y+1, term.c.x+1); 1877 | ttywrite(buf, len, 0); 1878 | break; 1879 | default: 1880 | goto unknown; 1881 | } 1882 | break; 1883 | case 'r': /* DECSTBM -- Set Scrolling Region */ 1884 | if (csiescseq.priv) { 1885 | goto unknown; 1886 | } else { 1887 | DEFAULT(csiescseq.arg[0], 1); 1888 | DEFAULT(csiescseq.arg[1], term.row); 1889 | tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 1890 | tmoveato(0, 0); 1891 | } 1892 | break; 1893 | case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1894 | tcursor(CURSOR_SAVE); 1895 | break; 1896 | case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1897 | tcursor(CURSOR_LOAD); 1898 | break; 1899 | case ' ': 1900 | switch (csiescseq.mode[1]) { 1901 | case 'q': /* DECSCUSR -- Set Cursor Style */ 1902 | if (xsetcursor(csiescseq.arg[0])) 1903 | goto unknown; 1904 | break; 1905 | default: 1906 | goto unknown; 1907 | } 1908 | break; 1909 | } 1910 | } 1911 | 1912 | void 1913 | csidump(void) 1914 | { 1915 | size_t i; 1916 | uint c; 1917 | 1918 | fprintf(stderr, "ESC["); 1919 | for (i = 0; i < csiescseq.len; i++) { 1920 | c = csiescseq.buf[i] & 0xff; 1921 | if (isprint(c)) { 1922 | putc(c, stderr); 1923 | } else if (c == '\n') { 1924 | fprintf(stderr, "(\\n)"); 1925 | } else if (c == '\r') { 1926 | fprintf(stderr, "(\\r)"); 1927 | } else if (c == 0x1b) { 1928 | fprintf(stderr, "(\\e)"); 1929 | } else { 1930 | fprintf(stderr, "(%02x)", c); 1931 | } 1932 | } 1933 | putc('\n', stderr); 1934 | } 1935 | 1936 | void 1937 | csireset(void) 1938 | { 1939 | memset(&csiescseq, 0, sizeof(csiescseq)); 1940 | } 1941 | 1942 | void 1943 | osc_color_response(int num, int index, int is_osc4) 1944 | { 1945 | int n; 1946 | char buf[32]; 1947 | unsigned char r, g, b; 1948 | 1949 | if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { 1950 | fprintf(stderr, "erresc: failed to fetch %s color %d\n", 1951 | is_osc4 ? "osc4" : "osc", 1952 | is_osc4 ? num : index); 1953 | return; 1954 | } 1955 | 1956 | n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", 1957 | is_osc4 ? "4;" : "", num, r, r, g, g, b, b); 1958 | if (n < 0 || n >= sizeof(buf)) { 1959 | fprintf(stderr, "error: %s while printing %s response\n", 1960 | n < 0 ? "snprintf failed" : "truncation occurred", 1961 | is_osc4 ? "osc4" : "osc"); 1962 | } else { 1963 | ttywrite(buf, n, 1); 1964 | } 1965 | } 1966 | 1967 | void 1968 | strhandle(void) 1969 | { 1970 | char *p = NULL, *dec; 1971 | int j, narg, par; 1972 | const struct { int idx; char *str; } osc_table[] = { 1973 | { defaultfg, "foreground" }, 1974 | { defaultbg, "background" }, 1975 | { defaultcs, "cursor" } 1976 | }; 1977 | 1978 | term.esc &= ~(ESC_STR_END|ESC_STR); 1979 | strparse(); 1980 | par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1981 | 1982 | switch (strescseq.type) { 1983 | case ']': /* OSC -- Operating System Command */ 1984 | switch (par) { 1985 | case 0: 1986 | if (narg > 1) { 1987 | xsettitle(strescseq.args[1]); 1988 | xseticontitle(strescseq.args[1]); 1989 | } 1990 | return; 1991 | case 1: 1992 | if (narg > 1) 1993 | xseticontitle(strescseq.args[1]); 1994 | return; 1995 | case 2: 1996 | if (narg > 1) 1997 | xsettitle(strescseq.args[1]); 1998 | return; 1999 | case 52: 2000 | if (narg > 2 && allowwindowops) { 2001 | dec = base64dec(strescseq.args[2]); 2002 | if (dec) { 2003 | xsetsel(dec); 2004 | xclipcopy(); 2005 | } else { 2006 | fprintf(stderr, "erresc: invalid base64\n"); 2007 | } 2008 | } 2009 | return; 2010 | case 10: 2011 | case 11: 2012 | case 12: 2013 | if (narg < 2) 2014 | break; 2015 | p = strescseq.args[1]; 2016 | if ((j = par - 10) < 0 || j >= LEN(osc_table)) 2017 | break; /* shouldn't be possible */ 2018 | 2019 | if (!strcmp(p, "?")) { 2020 | osc_color_response(par, osc_table[j].idx, 0); 2021 | } else if (xsetcolorname(osc_table[j].idx, p)) { 2022 | fprintf(stderr, "erresc: invalid %s color: %s\n", 2023 | osc_table[j].str, p); 2024 | } else { 2025 | tfulldirt(); 2026 | } 2027 | return; 2028 | case 4: /* color set */ 2029 | if (narg < 3) 2030 | break; 2031 | p = strescseq.args[2]; 2032 | /* FALLTHROUGH */ 2033 | case 104: /* color reset */ 2034 | j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 2035 | 2036 | if (p && !strcmp(p, "?")) { 2037 | osc_color_response(j, 0, 1); 2038 | } else if (xsetcolorname(j, p)) { 2039 | if (par == 104 && narg <= 1) { 2040 | xloadcols(); 2041 | return; /* color reset without parameter */ 2042 | } 2043 | fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", 2044 | j, p ? p : "(null)"); 2045 | } else { 2046 | /* 2047 | * TODO if defaultbg color is changed, borders 2048 | * are dirty 2049 | */ 2050 | tfulldirt(); 2051 | } 2052 | return; 2053 | } 2054 | break; 2055 | case 'k': /* old title set compatibility */ 2056 | xsettitle(strescseq.args[0]); 2057 | return; 2058 | case 'P': /* DCS -- Device Control String */ 2059 | case '_': /* APC -- Application Program Command */ 2060 | case '^': /* PM -- Privacy Message */ 2061 | return; 2062 | } 2063 | 2064 | fprintf(stderr, "erresc: unknown str "); 2065 | strdump(); 2066 | } 2067 | 2068 | void 2069 | strparse(void) 2070 | { 2071 | int c; 2072 | char *p = strescseq.buf; 2073 | 2074 | strescseq.narg = 0; 2075 | strescseq.buf[strescseq.len] = '\0'; 2076 | 2077 | if (*p == '\0') 2078 | return; 2079 | 2080 | while (strescseq.narg < STR_ARG_SIZ) { 2081 | strescseq.args[strescseq.narg++] = p; 2082 | while ((c = *p) != ';' && c != '\0') 2083 | ++p; 2084 | if (c == '\0') 2085 | return; 2086 | *p++ = '\0'; 2087 | } 2088 | } 2089 | 2090 | void 2091 | strdump(void) 2092 | { 2093 | size_t i; 2094 | uint c; 2095 | 2096 | fprintf(stderr, "ESC%c", strescseq.type); 2097 | for (i = 0; i < strescseq.len; i++) { 2098 | c = strescseq.buf[i] & 0xff; 2099 | if (c == '\0') { 2100 | putc('\n', stderr); 2101 | return; 2102 | } else if (isprint(c)) { 2103 | putc(c, stderr); 2104 | } else if (c == '\n') { 2105 | fprintf(stderr, "(\\n)"); 2106 | } else if (c == '\r') { 2107 | fprintf(stderr, "(\\r)"); 2108 | } else if (c == 0x1b) { 2109 | fprintf(stderr, "(\\e)"); 2110 | } else { 2111 | fprintf(stderr, "(%02x)", c); 2112 | } 2113 | } 2114 | fprintf(stderr, "ESC\\\n"); 2115 | } 2116 | 2117 | void 2118 | strreset(void) 2119 | { 2120 | strescseq = (STREscape){ 2121 | .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), 2122 | .siz = STR_BUF_SIZ, 2123 | }; 2124 | } 2125 | 2126 | void 2127 | sendbreak(const Arg *arg) 2128 | { 2129 | if (tcsendbreak(cmdfd, 0)) 2130 | perror("Error sending break"); 2131 | } 2132 | 2133 | void 2134 | tprinter(char *s, size_t len) 2135 | { 2136 | if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2137 | perror("Error writing to output file"); 2138 | close(iofd); 2139 | iofd = -1; 2140 | } 2141 | } 2142 | 2143 | void 2144 | toggleprinter(const Arg *arg) 2145 | { 2146 | term.mode ^= MODE_PRINT; 2147 | } 2148 | 2149 | void 2150 | printscreen(const Arg *arg) 2151 | { 2152 | tdump(); 2153 | } 2154 | 2155 | void 2156 | printsel(const Arg *arg) 2157 | { 2158 | tdumpsel(); 2159 | } 2160 | 2161 | void 2162 | tdumpsel(void) 2163 | { 2164 | char *ptr; 2165 | 2166 | if ((ptr = getsel())) { 2167 | tprinter(ptr, strlen(ptr)); 2168 | free(ptr); 2169 | } 2170 | } 2171 | 2172 | void 2173 | tdumpline(int n) 2174 | { 2175 | char buf[UTF_SIZ]; 2176 | const Glyph *bp, *end; 2177 | 2178 | bp = &TLINE(n)[0]; 2179 | end = &bp[MIN(tlinelen(n), term.col) - 1]; 2180 | if (bp != end || bp->u != ' ') { 2181 | for ( ; bp <= end; ++bp) 2182 | tprinter(buf, utf8encode(bp->u, buf)); 2183 | } 2184 | tprinter("\n", 1); 2185 | } 2186 | 2187 | void 2188 | tdump(void) 2189 | { 2190 | int i; 2191 | 2192 | for (i = 0; i < term.row; ++i) 2193 | tdumpline(i); 2194 | } 2195 | 2196 | void 2197 | tputtab(int n) 2198 | { 2199 | uint x = term.c.x; 2200 | 2201 | if (n > 0) { 2202 | while (x < term.col && n--) 2203 | for (++x; x < term.col && !term.tabs[x]; ++x) 2204 | /* nothing */ ; 2205 | } else if (n < 0) { 2206 | while (x > 0 && n++) 2207 | for (--x; x > 0 && !term.tabs[x]; --x) 2208 | /* nothing */ ; 2209 | } 2210 | term.c.x = LIMIT(x, 0, term.col-1); 2211 | } 2212 | 2213 | void 2214 | tdefutf8(char ascii) 2215 | { 2216 | if (ascii == 'G') 2217 | term.mode |= MODE_UTF8; 2218 | else if (ascii == '@') 2219 | term.mode &= ~MODE_UTF8; 2220 | } 2221 | 2222 | void 2223 | tdeftran(char ascii) 2224 | { 2225 | static char cs[] = "0B"; 2226 | static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2227 | char *p; 2228 | 2229 | if ((p = strchr(cs, ascii)) == NULL) { 2230 | fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2231 | } else { 2232 | term.trantbl[term.icharset] = vcs[p - cs]; 2233 | } 2234 | } 2235 | 2236 | void 2237 | tdectest(char c) 2238 | { 2239 | int x, y; 2240 | 2241 | if (c == '8') { /* DEC screen alignment test. */ 2242 | for (x = 0; x < term.col; ++x) { 2243 | for (y = 0; y < term.row; ++y) 2244 | tsetchar('E', &term.c.attr, x, y); 2245 | } 2246 | } 2247 | } 2248 | 2249 | void 2250 | tstrsequence(uchar c) 2251 | { 2252 | switch (c) { 2253 | case 0x90: /* DCS -- Device Control String */ 2254 | c = 'P'; 2255 | break; 2256 | case 0x9f: /* APC -- Application Program Command */ 2257 | c = '_'; 2258 | break; 2259 | case 0x9e: /* PM -- Privacy Message */ 2260 | c = '^'; 2261 | break; 2262 | case 0x9d: /* OSC -- Operating System Command */ 2263 | c = ']'; 2264 | break; 2265 | } 2266 | strreset(); 2267 | strescseq.type = c; 2268 | term.esc |= ESC_STR; 2269 | } 2270 | 2271 | void 2272 | tcontrolcode(uchar ascii) 2273 | { 2274 | switch (ascii) { 2275 | case '\t': /* HT */ 2276 | tputtab(1); 2277 | return; 2278 | case '\b': /* BS */ 2279 | tmoveto(term.c.x-1, term.c.y); 2280 | return; 2281 | case '\r': /* CR */ 2282 | tmoveto(0, term.c.y); 2283 | return; 2284 | case '\f': /* LF */ 2285 | case '\v': /* VT */ 2286 | case '\n': /* LF */ 2287 | /* go to first col if the mode is set */ 2288 | tnewline(IS_SET(MODE_CRLF)); 2289 | return; 2290 | case '\a': /* BEL */ 2291 | if (term.esc & ESC_STR_END) { 2292 | /* backwards compatibility to xterm */ 2293 | strhandle(); 2294 | } else { 2295 | xbell(); 2296 | } 2297 | break; 2298 | case '\033': /* ESC */ 2299 | csireset(); 2300 | term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2301 | term.esc |= ESC_START; 2302 | return; 2303 | case '\016': /* SO (LS1 -- Locking shift 1) */ 2304 | case '\017': /* SI (LS0 -- Locking shift 0) */ 2305 | term.charset = 1 - (ascii - '\016'); 2306 | return; 2307 | case '\032': /* SUB */ 2308 | tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2309 | /* FALLTHROUGH */ 2310 | case '\030': /* CAN */ 2311 | csireset(); 2312 | break; 2313 | case '\005': /* ENQ (IGNORED) */ 2314 | case '\000': /* NUL (IGNORED) */ 2315 | case '\021': /* XON (IGNORED) */ 2316 | case '\023': /* XOFF (IGNORED) */ 2317 | case 0177: /* DEL (IGNORED) */ 2318 | return; 2319 | case 0x80: /* TODO: PAD */ 2320 | case 0x81: /* TODO: HOP */ 2321 | case 0x82: /* TODO: BPH */ 2322 | case 0x83: /* TODO: NBH */ 2323 | case 0x84: /* TODO: IND */ 2324 | break; 2325 | case 0x85: /* NEL -- Next line */ 2326 | tnewline(1); /* always go to first col */ 2327 | break; 2328 | case 0x86: /* TODO: SSA */ 2329 | case 0x87: /* TODO: ESA */ 2330 | break; 2331 | case 0x88: /* HTS -- Horizontal tab stop */ 2332 | term.tabs[term.c.x] = 1; 2333 | break; 2334 | case 0x89: /* TODO: HTJ */ 2335 | case 0x8a: /* TODO: VTS */ 2336 | case 0x8b: /* TODO: PLD */ 2337 | case 0x8c: /* TODO: PLU */ 2338 | case 0x8d: /* TODO: RI */ 2339 | case 0x8e: /* TODO: SS2 */ 2340 | case 0x8f: /* TODO: SS3 */ 2341 | case 0x91: /* TODO: PU1 */ 2342 | case 0x92: /* TODO: PU2 */ 2343 | case 0x93: /* TODO: STS */ 2344 | case 0x94: /* TODO: CCH */ 2345 | case 0x95: /* TODO: MW */ 2346 | case 0x96: /* TODO: SPA */ 2347 | case 0x97: /* TODO: EPA */ 2348 | case 0x98: /* TODO: SOS */ 2349 | case 0x99: /* TODO: SGCI */ 2350 | break; 2351 | case 0x9a: /* DECID -- Identify Terminal */ 2352 | ttywrite(vtiden, strlen(vtiden), 0); 2353 | break; 2354 | case 0x9b: /* TODO: CSI */ 2355 | case 0x9c: /* TODO: ST */ 2356 | break; 2357 | case 0x90: /* DCS -- Device Control String */ 2358 | case 0x9d: /* OSC -- Operating System Command */ 2359 | case 0x9e: /* PM -- Privacy Message */ 2360 | case 0x9f: /* APC -- Application Program Command */ 2361 | tstrsequence(ascii); 2362 | return; 2363 | } 2364 | /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2365 | term.esc &= ~(ESC_STR_END|ESC_STR); 2366 | } 2367 | 2368 | /* 2369 | * returns 1 when the sequence is finished and it hasn't to read 2370 | * more characters for this sequence, otherwise 0 2371 | */ 2372 | int 2373 | eschandle(uchar ascii) 2374 | { 2375 | switch (ascii) { 2376 | case '[': 2377 | term.esc |= ESC_CSI; 2378 | return 0; 2379 | case '#': 2380 | term.esc |= ESC_TEST; 2381 | return 0; 2382 | case '%': 2383 | term.esc |= ESC_UTF8; 2384 | return 0; 2385 | case 'P': /* DCS -- Device Control String */ 2386 | case '_': /* APC -- Application Program Command */ 2387 | case '^': /* PM -- Privacy Message */ 2388 | case ']': /* OSC -- Operating System Command */ 2389 | case 'k': /* old title set compatibility */ 2390 | tstrsequence(ascii); 2391 | return 0; 2392 | case 'n': /* LS2 -- Locking shift 2 */ 2393 | case 'o': /* LS3 -- Locking shift 3 */ 2394 | term.charset = 2 + (ascii - 'n'); 2395 | break; 2396 | case '(': /* GZD4 -- set primary charset G0 */ 2397 | case ')': /* G1D4 -- set secondary charset G1 */ 2398 | case '*': /* G2D4 -- set tertiary charset G2 */ 2399 | case '+': /* G3D4 -- set quaternary charset G3 */ 2400 | term.icharset = ascii - '('; 2401 | term.esc |= ESC_ALTCHARSET; 2402 | return 0; 2403 | case 'D': /* IND -- Linefeed */ 2404 | if (term.c.y == term.bot) { 2405 | tscrollup(term.top, 1); 2406 | } else { 2407 | tmoveto(term.c.x, term.c.y+1); 2408 | } 2409 | break; 2410 | case 'E': /* NEL -- Next line */ 2411 | tnewline(1); /* always go to first col */ 2412 | break; 2413 | case 'H': /* HTS -- Horizontal tab stop */ 2414 | term.tabs[term.c.x] = 1; 2415 | break; 2416 | case 'M': /* RI -- Reverse index */ 2417 | if (term.c.y == term.top) { 2418 | tscrolldown(term.top, 1); 2419 | } else { 2420 | tmoveto(term.c.x, term.c.y-1); 2421 | } 2422 | break; 2423 | case 'Z': /* DECID -- Identify Terminal */ 2424 | ttywrite(vtiden, strlen(vtiden), 0); 2425 | break; 2426 | case 'c': /* RIS -- Reset to initial state */ 2427 | treset(); 2428 | resettitle(); 2429 | xloadcols(); 2430 | xsetmode(0, MODE_HIDE); 2431 | break; 2432 | case '=': /* DECPAM -- Application keypad */ 2433 | xsetmode(1, MODE_APPKEYPAD); 2434 | break; 2435 | case '>': /* DECPNM -- Normal keypad */ 2436 | xsetmode(0, MODE_APPKEYPAD); 2437 | break; 2438 | case '7': /* DECSC -- Save Cursor */ 2439 | tcursor(CURSOR_SAVE); 2440 | break; 2441 | case '8': /* DECRC -- Restore Cursor */ 2442 | tcursor(CURSOR_LOAD); 2443 | break; 2444 | case '\\': /* ST -- String Terminator */ 2445 | if (term.esc & ESC_STR_END) 2446 | strhandle(); 2447 | break; 2448 | default: 2449 | fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2450 | (uchar) ascii, isprint(ascii)? ascii:'.'); 2451 | break; 2452 | } 2453 | return 1; 2454 | } 2455 | 2456 | void 2457 | tputc(Rune u) 2458 | { 2459 | char c[UTF_SIZ]; 2460 | int control; 2461 | int width, len; 2462 | Glyph *gp; 2463 | 2464 | control = ISCONTROL(u); 2465 | if (u < 127 || !IS_SET(MODE_UTF8)) { 2466 | c[0] = u; 2467 | width = len = 1; 2468 | } else { 2469 | len = utf8encode(u, c); 2470 | if (!control && (width = wcwidth(u)) == -1) 2471 | width = 1; 2472 | } 2473 | 2474 | if (IS_SET(MODE_PRINT)) 2475 | tprinter(c, len); 2476 | 2477 | /* 2478 | * STR sequence must be checked before anything else 2479 | * because it uses all following characters until it 2480 | * receives a ESC, a SUB, a ST or any other C1 control 2481 | * character. 2482 | */ 2483 | if (term.esc & ESC_STR) { 2484 | if (u == '\a' || u == 030 || u == 032 || u == 033 || 2485 | ISCONTROLC1(u)) { 2486 | term.esc &= ~(ESC_START|ESC_STR); 2487 | term.esc |= ESC_STR_END; 2488 | goto check_control_code; 2489 | } 2490 | 2491 | if (strescseq.len+len >= strescseq.siz) { 2492 | /* 2493 | * Here is a bug in terminals. If the user never sends 2494 | * some code to stop the str or esc command, then st 2495 | * will stop responding. But this is better than 2496 | * silently failing with unknown characters. At least 2497 | * then users will report back. 2498 | * 2499 | * In the case users ever get fixed, here is the code: 2500 | */ 2501 | /* 2502 | * term.esc = 0; 2503 | * strhandle(); 2504 | */ 2505 | if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) 2506 | return; 2507 | strescseq.siz *= 2; 2508 | strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); 2509 | } 2510 | 2511 | memmove(&strescseq.buf[strescseq.len], c, len); 2512 | strescseq.len += len; 2513 | return; 2514 | } 2515 | 2516 | check_control_code: 2517 | /* 2518 | * Actions of control codes must be performed as soon they arrive 2519 | * because they can be embedded inside a control sequence, and 2520 | * they must not cause conflicts with sequences. 2521 | */ 2522 | if (control) { 2523 | /* in UTF-8 mode ignore handling C1 control characters */ 2524 | if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) 2525 | return; 2526 | tcontrolcode(u); 2527 | /* 2528 | * control codes are not shown ever 2529 | */ 2530 | if (!term.esc) 2531 | term.lastc = 0; 2532 | return; 2533 | } else if (term.esc & ESC_START) { 2534 | if (term.esc & ESC_CSI) { 2535 | csiescseq.buf[csiescseq.len++] = u; 2536 | if (BETWEEN(u, 0x40, 0x7E) 2537 | || csiescseq.len >= \ 2538 | sizeof(csiescseq.buf)-1) { 2539 | term.esc = 0; 2540 | csiparse(); 2541 | csihandle(); 2542 | } 2543 | return; 2544 | } else if (term.esc & ESC_UTF8) { 2545 | tdefutf8(u); 2546 | } else if (term.esc & ESC_ALTCHARSET) { 2547 | tdeftran(u); 2548 | } else if (term.esc & ESC_TEST) { 2549 | tdectest(u); 2550 | } else { 2551 | if (!eschandle(u)) 2552 | return; 2553 | /* sequence already finished */ 2554 | } 2555 | term.esc = 0; 2556 | /* 2557 | * All characters which form part of a sequence are not 2558 | * printed 2559 | */ 2560 | return; 2561 | } 2562 | if (selected(term.c.x, term.c.y)) 2563 | selclear(); 2564 | 2565 | gp = &TLINE(term.c.y)[term.c.x]; 2566 | if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2567 | gp->mode |= ATTR_WRAP; 2568 | tnewline(1); 2569 | gp = &TLINE(term.c.y)[term.c.x]; 2570 | } 2571 | 2572 | if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { 2573 | memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 2574 | gp->mode &= ~ATTR_WIDE; 2575 | } 2576 | 2577 | if (term.c.x+width > term.col) { 2578 | if (IS_SET(MODE_WRAP)) 2579 | tnewline(1); 2580 | else 2581 | tmoveto(term.col - width, term.c.y); 2582 | gp = &TLINE(term.c.y)[term.c.x]; 2583 | } 2584 | 2585 | tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2586 | term.lastc = u; 2587 | 2588 | if (width == 2) { 2589 | gp->mode |= ATTR_WIDE; 2590 | if (term.c.x+1 < term.col) { 2591 | if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { 2592 | gp[2].u = ' '; 2593 | gp[2].mode &= ~ATTR_WDUMMY; 2594 | } 2595 | gp[1].u = '\0'; 2596 | gp[1].mode = ATTR_WDUMMY; 2597 | } 2598 | } 2599 | if (term.c.x+width < term.col) { 2600 | tmoveto(term.c.x+width, term.c.y); 2601 | } else { 2602 | term.c.state |= CURSOR_WRAPNEXT; 2603 | } 2604 | } 2605 | 2606 | int 2607 | twrite(const char *buf, int buflen, int show_ctrl) 2608 | { 2609 | int charsize; 2610 | Rune u; 2611 | int n; 2612 | 2613 | if (TSCREEN.off) { 2614 | TSCREEN.off = 0; 2615 | tfulldirt(); 2616 | } 2617 | 2618 | for (n = 0; n < buflen; n += charsize) { 2619 | if (IS_SET(MODE_UTF8)) { 2620 | /* process a complete utf8 char */ 2621 | charsize = utf8decode(buf + n, &u, buflen - n); 2622 | if (charsize == 0) 2623 | break; 2624 | } else { 2625 | u = buf[n] & 0xFF; 2626 | charsize = 1; 2627 | } 2628 | if (show_ctrl && ISCONTROL(u)) { 2629 | if (u & 0x80) { 2630 | u &= 0x7f; 2631 | tputc('^'); 2632 | tputc('['); 2633 | } else if (u != '\n' && u != '\r' && u != '\t') { 2634 | u ^= 0x40; 2635 | tputc('^'); 2636 | } 2637 | } 2638 | tputc(u); 2639 | } 2640 | return n; 2641 | } 2642 | 2643 | void 2644 | clearline(Line line, Glyph g, int x, int xend) 2645 | { 2646 | int i; 2647 | g.mode = 0; 2648 | g.u = ' '; 2649 | for (i = x; i < xend; ++i) { 2650 | line[i] = g; 2651 | } 2652 | } 2653 | 2654 | Line 2655 | ensureline(Line line) 2656 | { 2657 | if (!line) { 2658 | line = xmalloc(term.linelen * sizeof(Glyph)); 2659 | } 2660 | return line; 2661 | } 2662 | 2663 | void 2664 | tresize(int col, int row) 2665 | { 2666 | int i, j; 2667 | int minrow = MIN(row, term.row); 2668 | int mincol = MIN(col, term.col); 2669 | int linelen = MAX(col, term.linelen); 2670 | int *bp; 2671 | 2672 | if (col < 1 || row < 1 || row > HISTSIZE) { 2673 | fprintf(stderr, 2674 | "tresize: error resizing to %dx%d\n", col, row); 2675 | return; 2676 | } 2677 | 2678 | /* Shift buffer to keep the cursor where we expect it */ 2679 | if (row <= term.c.y) { 2680 | term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size; 2681 | } 2682 | 2683 | /* Resize and clear line buffers as needed */ 2684 | if (linelen > term.linelen) { 2685 | for (i = 0; i < term.screen[0].size; ++i) { 2686 | if (term.screen[0].buffer[i]) { 2687 | term.screen[0].buffer[i] = xrealloc(term.screen[0].buffer[i], linelen * sizeof(Glyph)); 2688 | clearline(term.screen[0].buffer[i], term.c.attr, term.linelen, linelen); 2689 | } 2690 | } 2691 | for (i = 0; i < minrow; ++i) { 2692 | term.screen[1].buffer[i] = xrealloc(term.screen[1].buffer[i], linelen * sizeof(Glyph)); 2693 | clearline(term.screen[1].buffer[i], term.c.attr, term.linelen, linelen); 2694 | } 2695 | } 2696 | /* Allocate all visible lines for regular line buffer */ 2697 | for (j = term.screen[0].cur, i = 0; i < row; ++i, j = (j + 1) % term.screen[0].size) 2698 | { 2699 | if (!term.screen[0].buffer[j]) { 2700 | term.screen[0].buffer[j] = xmalloc(linelen * sizeof(Glyph)); 2701 | } 2702 | if (i >= term.row) { 2703 | clearline(term.screen[0].buffer[j], term.c.attr, 0, linelen); 2704 | } 2705 | } 2706 | /* Resize alt screen */ 2707 | term.screen[1].cur = 0; 2708 | term.screen[1].size = row; 2709 | for (i = row; i < term.row; ++i) { 2710 | free(term.screen[1].buffer[i]); 2711 | } 2712 | term.screen[1].buffer = xrealloc(term.screen[1].buffer, row * sizeof(Line)); 2713 | for (i = term.row; i < row; ++i) { 2714 | term.screen[1].buffer[i] = xmalloc(linelen * sizeof(Glyph)); 2715 | clearline(term.screen[1].buffer[i], term.c.attr, 0, linelen); 2716 | } 2717 | 2718 | /* resize to new height */ 2719 | term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2720 | term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2721 | 2722 | /* fix tabstops */ 2723 | if (col > term.col) { 2724 | bp = term.tabs + term.col; 2725 | 2726 | memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2727 | while (--bp > term.tabs && !*bp) 2728 | /* nothing */ ; 2729 | for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2730 | *bp = 1; 2731 | } 2732 | 2733 | /* update terminal size */ 2734 | term.col = col; 2735 | term.row = row; 2736 | term.linelen = linelen; 2737 | /* reset scrolling region */ 2738 | tsetscroll(0, row-1); 2739 | /* make use of the LIMIT in tmoveto */ 2740 | tmoveto(term.c.x, term.c.y); 2741 | tfulldirt(); 2742 | } 2743 | 2744 | void 2745 | resettitle(void) 2746 | { 2747 | xsettitle(NULL); 2748 | } 2749 | 2750 | void 2751 | drawregion(int x1, int y1, int x2, int y2) 2752 | { 2753 | int y, L; 2754 | 2755 | L = TLINEOFFSET(y1); 2756 | for (y = y1; y < y2; y++) { 2757 | if (term.dirty[y]) { 2758 | term.dirty[y] = 0; 2759 | xdrawline(TSCREEN.buffer[L], x1, y, x2); 2760 | } 2761 | L = (L + 1) % TSCREEN.size; 2762 | } 2763 | } 2764 | 2765 | void 2766 | draw(void) 2767 | { 2768 | int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; 2769 | 2770 | if (!xstartdraw()) 2771 | return; 2772 | 2773 | /* adjust cursor position */ 2774 | LIMIT(term.ocx, 0, term.col-1); 2775 | LIMIT(term.ocy, 0, term.row-1); 2776 | if (TLINE(term.ocy)[term.ocx].mode & ATTR_WDUMMY) 2777 | term.ocx--; 2778 | if (TLINE(term.c.y)[cx].mode & ATTR_WDUMMY) 2779 | cx--; 2780 | 2781 | drawregion(0, 0, term.col, term.row); 2782 | if (TSCREEN.off == 0) 2783 | xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], 2784 | term.ocx, term.ocy, TLINE(term.ocy)[term.ocx], 2785 | TLINE(term.ocy), term.col); 2786 | 2787 | term.ocx = cx; 2788 | term.ocy = term.c.y; 2789 | xfinishdraw(); 2790 | if (ocx != term.ocx || ocy != term.ocy) 2791 | xximspot(term.ocx, term.ocy); 2792 | } 2793 | 2794 | void 2795 | redraw(void) 2796 | { 2797 | tfulldirt(); 2798 | draw(); 2799 | } 2800 | --------------------------------------------------------------------------------